diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ca5061e4..722bea0a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -54,6 +54,7 @@ phutil_register_library_map(array( 'ArcanistCommentStyleXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php', 'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php', 'ArcanistCompilerLintRenderer' => 'lint/renderer/ArcanistCompilerLintRenderer.php', + 'ArcanistComposerLinter' => 'lint/linter/ArcanistComposerLinter.php', 'ArcanistComprehensiveLintEngine' => 'lint/engine/ArcanistComprehensiveLintEngine.php', 'ArcanistConcatenationOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php', 'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php', @@ -337,6 +338,7 @@ phutil_register_library_map(array( 'ArcanistCommentStyleXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCommitWorkflow' => 'ArcanistWorkflow', 'ArcanistCompilerLintRenderer' => 'ArcanistLintRenderer', + 'ArcanistComposerLinter' => 'ArcanistLinter', 'ArcanistComprehensiveLintEngine' => 'ArcanistLintEngine', 'ArcanistConcatenationOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistConfiguration' => 'Phobject', diff --git a/src/lint/engine/ArcanistLintEngine.php b/src/lint/engine/ArcanistLintEngine.php index 720af8ef..8add0a88 100644 --- a/src/lint/engine/ArcanistLintEngine.php +++ b/src/lint/engine/ArcanistLintEngine.php @@ -471,6 +471,8 @@ abstract class ArcanistLintEngine extends Phobject { } private function executeLinters(array $runnable) { + assert_instances_of($runnable, 'ArcanistLinter'); + $all_paths = $this->getPaths(); $path_chunks = array_chunk($all_paths, 32, $preserve_keys = true); diff --git a/src/lint/linter/ArcanistComposerLinter.php b/src/lint/linter/ArcanistComposerLinter.php new file mode 100644 index 00000000..02b7c673 --- /dev/null +++ b/src/lint/linter/ArcanistComposerLinter.php @@ -0,0 +1,55 @@ + pht('Lock file out-of-date'), + ); + } + + public function lintPath($path) { + switch (basename($path)) { + case 'composer.json': + $this->lintComposerJson($path); + break; + case 'composer.lock': + break; + } + } + + private function lintComposerJson($path) { + $composer_hash = md5(Filesystem::readFile(dirname($path).'/composer.json')); + $composer_lock = phutil_json_decode( + Filesystem::readFile(dirname($path).'/composer.lock')); + + if ($composer_hash !== $composer_lock['hash']) { + $this->raiseLintAtPath( + self::LINT_OUT_OF_DATE, + pht( + "The '%s' file seems to be out-of-date. ". + "You probably need to run `%s`.", + 'composer.lock', + 'composer update')); + } + } + +} diff --git a/src/lint/linter/ArcanistInlineHTMLXHPASTLinterRule.php b/src/lint/linter/ArcanistInlineHTMLXHPASTLinterRule.php index 3cb758ad..701b7261 100644 --- a/src/lint/linter/ArcanistInlineHTMLXHPASTLinterRule.php +++ b/src/lint/linter/ArcanistInlineHTMLXHPASTLinterRule.php @@ -22,9 +22,14 @@ final class ArcanistInlineHTMLXHPASTLinterRule continue; } + if (preg_match('/^\s*$/', $html->getValue())) { + continue; + } + $this->raiseLintAtToken( $html, pht('PHP files must only contain PHP code.')); + break; } } diff --git a/src/lint/linter/ArcanistPyLintLinter.php b/src/lint/linter/ArcanistPyLintLinter.php index 111cc27e..4884ab9f 100644 --- a/src/lint/linter/ArcanistPyLintLinter.php +++ b/src/lint/linter/ArcanistPyLintLinter.php @@ -82,7 +82,7 @@ final class ArcanistPyLintLinter extends ArcanistExternalLinter { $options = array(); $options[] = '--reports=no'; - $options[] = '--msg-template="{line}|{column}|{msg_id}|{symbol}|{msg}"'; + $options[] = '--msg-template={line}|{column}|{msg_id}|{symbol}|{msg}'; // Specify an `--rcfile`, either absolute or relative to the project root. // Stupidly, the command line args above are overridden by rcfile, so be diff --git a/src/lint/linter/__tests__/xhpast/array-formatting.lint-test b/src/lint/linter/__tests__/xhpast/array-formatting.lint-test index 3dda317d..7ae23f5d 100644 --- a/src/lint/linter/__tests__/xhpast/array-formatting.lint-test +++ b/src/lint/linter/__tests__/xhpast/array-formatting.lint-test @@ -2,6 +2,7 @@ array ( 1, 2, 3 ); list ( $x, $y ) = array(); +[ 1, 2 , 3 ]; ~~~~~~~~~~ warning:3:6 warning:3:8 @@ -9,8 +10,11 @@ warning:3:16 warning:4:5 warning:4:7 warning:4:14 +warning:5:2 +warning:5:11 ~~~~~~~~~~ ~~~~~~~~~~ +disabled:1:1 error:1:1 ~~~~~~~~~~ garbage garbage diff --git a/src/lint/linter/__tests__/xhpast/undeclared-variables.lint-test b/src/lint/linter/__tests__/xhpast/undeclared-variables.lint-test index 1bedf0fa..ad03c44c 100644 --- a/src/lint/linter/__tests__/xhpast/undeclared-variables.lint-test +++ b/src/lint/linter/__tests__/xhpast/undeclared-variables.lint-test @@ -179,6 +179,12 @@ function some_func($x, $y) { echo $z; }; } + +function some_func($x, $y) { + $func = function ($z) use ($x) { + echo "$x/$y/$z"; + }; +} ~~~~~~~~~~ warning:9:3 error:28:3 @@ -201,3 +207,4 @@ error:150:9 error:164:9 error:171:5 error:178:10 +error:185:14 diff --git a/src/lint/linter/xhpast/rules/ArcanistCallParenthesesXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistCallParenthesesXHPASTLinterRule.php index 67cb3676..594399b3 100644 --- a/src/lint/linter/xhpast/rules/ArcanistCallParenthesesXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/rules/ArcanistCallParenthesesXHPASTLinterRule.php @@ -24,6 +24,11 @@ final class ArcanistCallParenthesesXHPASTLinterRule foreach ($nodes as $node) { switch ($node->getTypeName()) { case 'n_ARRAY_LITERAL': + if (head($node->getTokens())->getTypeName() == '[') { + // Short array syntax. + continue 2; + } + $params = $node->getChildOfType(0, 'n_ARRAY_VALUE_LIST'); break; @@ -44,6 +49,7 @@ final class ArcanistCallParenthesesXHPASTLinterRule $tokens = $params->getTokens(); $first = head($tokens); + $leading = $first->getNonsemanticTokensBefore(); $leading_text = implode('', mpull($leading, 'getValue')); if (preg_match('/^\s+$/', $leading_text)) { diff --git a/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php index 7ad16a44..7d7f8c4d 100644 --- a/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php @@ -29,12 +29,6 @@ final class ArcanistParenthesesSpacingXHPASTLinterRule $token_o = array_shift($tokens); $token_c = array_pop($tokens); - if ($token_o->getTypeName() !== '(') { - throw new Exception(pht('Expected open parentheses.')); - } - if ($token_c->getTypeName() !== ')') { - throw new Exception(pht('Expected close parentheses.')); - } $nonsem_o = $token_o->getNonsemanticTokensAfter(); $nonsem_c = $token_c->getNonsemanticTokensBefore(); diff --git a/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php index 05464d41..2a389e34 100644 --- a/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php @@ -68,6 +68,7 @@ final class ArcanistUndeclaredVariableXHPASTLinterRule ) + array_fill_keys($this->getSuperGlobalNames(), 0); $declaration_tokens = array(); $exclude_tokens = array(); + $exclude_strings = array(); $vars = array(); // First up, find all the different kinds of declarations, as explained @@ -175,6 +176,16 @@ final class ArcanistUndeclaredVariableXHPASTLinterRule foreach ($func_decl->selectDescendantsOfType('n_VARIABLE') as $var) { $exclude_tokens[$var->getID()] = true; } + + foreach (array('n_STRING_SCALAR', 'n_HEREDOC') as $type) { + foreach ($func_decl->selectDescendantsOfType($type) as $string) { + $exclude_strings[$string->getID()] = array(); + + foreach ($string->getStringVariables() as $offset => $var) { + $exclude_strings[$string->getID()][$var] = true; + } + } + } } // Now we have every declaration except foreach(), handled below. Build @@ -316,6 +327,10 @@ final class ArcanistUndeclaredVariableXHPASTLinterRule foreach (array('n_STRING_SCALAR', 'n_HEREDOC') as $type) { foreach ($body->selectDescendantsOfType($type) as $string) { foreach ($string->getStringVariables() as $offset => $var) { + if (isset($exclude_strings[$string->getID()][$var])) { + continue; + } + $all[$string->getOffset() + $offset - 1] = '$'.$var; } } diff --git a/src/workflow/ArcanistLandWorkflow.php b/src/workflow/ArcanistLandWorkflow.php index 98c804b6..8f4356ee 100644 --- a/src/workflow/ArcanistLandWorkflow.php +++ b/src/workflow/ArcanistLandWorkflow.php @@ -492,17 +492,34 @@ EOTEXT 'arc amend', '--revision ')); } else if (count($revisions) > 1) { - $message = pht( - "There are multiple revisions on feature %s '%s' which are not ". - "present on '%s':\n\n". - "%s\n". - "Separate these revisions onto different %s, or use --revision ' ". - "to use the commit message from and land them all.", - $this->branchType, - $this->branch, - $this->onto, - $this->renderRevisionList($revisions), - $this->branchType.'s'); + switch ($this->branchType) { + case self::REFTYPE_BOOKMARK: + $message = pht( + "There are multiple revisions on feature bookmark '%s' which are ". + "not present on '%s':\n\n". + "%s\n". + 'Separate these revisions onto different bookmarks, or use '. + '--revision to use the commit message from '. + 'and land them all.', + $this->branch, + $this->onto, + $this->renderRevisionList($revisions)); + break; + case self::REFTYPE_BRANCH: + default: + $message = pht( + "There are multiple revisions on feature branch '%s' which are ". + "not present on '%s':\n\n". + "%s\n". + 'Separate these revisions onto different branches, or use '. + '--revision to use the commit message from '. + 'and land them all.', + $this->branch, + $this->onto, + $this->renderRevisionList($revisions)); + break; + } + throw new ArcanistUsageException($message); }