diff --git a/src/lint/linter/ArcanistLicenseLinter.php b/src/lint/linter/ArcanistLicenseLinter.php index 024c88fa..03401f63 100644 --- a/src/lint/linter/ArcanistLicenseLinter.php +++ b/src/lint/linter/ArcanistLicenseLinter.php @@ -1,68 +1,71 @@ 'No License Header', ); } /** * Given the name of the copyright holder, return appropriate license header * text. */ abstract protected function getLicenseText($copyright_holder); /** * Return an array of regular expressions that, if matched, indicate * that a copyright header is required. The appropriate match will be * stripped from the input when comparing against the expected license. */ abstract protected function getLicensePatterns(); public function lintPath($path) { - $working_copy = $this->getEngine()->getWorkingCopy(); - $copyright_holder = $working_copy->getConfig('copyright_holder'); + $copyright_holder = $this->getConfig('copyright_holder'); + if ($copyright_holder === null) { + $working_copy = $this->getEngine()->getWorkingCopy(); + $copyright_holder = $working_copy->getConfig('copyright_holder'); + } if (!$copyright_holder) { return; } $patterns = $this->getLicensePatterns(); $license = $this->getLicenseText($copyright_holder); $data = $this->getData($path); $matches = 0; foreach ($patterns as $pattern) { if (preg_match($pattern, $data, $matches)) { $expect = rtrim(implode('', array_slice($matches, 1)))."\n".$license; if (trim($matches[0]) != trim($expect)) { $this->raiseLintAtOffset( 0, self::LINT_NO_LICENSE_HEADER, 'This file has a missing or out of date license header.', $matches[0], ltrim($expect)); } break; } } } } diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php index cf3da352..2b5dd53b 100644 --- a/src/lint/linter/ArcanistLinter.php +++ b/src/lint/linter/ArcanistLinter.php @@ -1,200 +1,210 @@ customSeverityMap = $map; return $this; } + public function setConfig(array $config) { + $this->config = $config; + return $this; + } + + protected function getConfig($key, $default = null) { + return idx($this->config, $key, $default); + } + public function getActivePath() { return $this->activePath; } public function stopAllLinters() { $this->stopAllLinters = true; return $this; } public function didStopAllLinters() { return $this->stopAllLinters; } public function addPath($path) { $this->paths[$path] = $path; return $this; } public function setPaths(array $paths) { $this->paths = $paths; return $this; } public function getPaths() { return array_values($this->paths); } public function addData($path, $data) { $this->data[$path] = $data; return $this; } protected function getData($path) { if (!array_key_exists($path, $this->data)) { $this->data[$path] = $this->getEngine()->loadData($path); } return $this->data[$path]; } public function setEngine(ArcanistLintEngine $engine) { $this->engine = $engine; return $this; } protected function getEngine() { return $this->engine; } public function getLintMessageFullCode($short_code) { return $this->getLinterName().$short_code; } public function getLintMessageSeverity($code) { $map = $this->customSeverityMap; if (isset($map[$code])) { return $map[$code]; } $map = $this->getLintSeverityMap(); if (isset($map[$code])) { return $map[$code]; } return ArcanistLintSeverity::SEVERITY_ERROR; } public function isMessageEnabled($code) { return ($this->getLintMessageSeverity($code) !== ArcanistLintSeverity::SEVERITY_DISABLED); } public function getLintMessageName($code) { $map = $this->getLintNameMap(); if (isset($map[$code])) { return $map[$code]; } return "Unknown lint message!"; } protected function addLintMessage(ArcanistLintMessage $message) { if (!$this->getEngine()->getCommitHookMode()) { $root = $this->getEngine()->getWorkingCopy()->getProjectRoot(); $path = Filesystem::resolvePath($message->getPath(), $root); $message->setPath(Filesystem::readablePath($path, $root)); } $this->messages[] = $message; return $message; } public function getLintMessages() { return $this->messages; } protected function raiseLintAtLine( $line, $char, $code, $desc, $original = null, $replacement = null) { $dict = array( 'path' => $this->getActivePath(), 'line' => $line, 'char' => $char, 'code' => $this->getLintMessageFullCode($code), 'severity' => $this->getLintMessageSeverity($code), 'name' => $this->getLintMessageName($code), 'description' => $desc, ); if ($original !== null) { $dict['original'] = $original; } if ($replacement !== null) { $dict['replacement'] = $replacement; } return $this->addLintMessage(ArcanistLintMessage::newFromDictionary($dict)); } protected function raiseLintAtPath( $code, $desc) { return $this->raiseLintAtLine(null, null, $code, $desc, null, null); } protected function raiseLintAtOffset( $offset, $code, $desc, $original = null, $replacement = null) { $path = $this->getActivePath(); $engine = $this->getEngine(); if ($offset === null) { $line = null; $char = null; } else { list($line, $char) = $engine->getLineAndCharFromOffset($path, $offset); } return $this->raiseLintAtLine( $line + 1, $char + 1, $code, $desc, $original, $replacement); } public function willLintPath($path) { $this->stopAllLinters = false; $this->activePath = $path; } public function canRun() { return true; } abstract public function willLintPaths(array $paths); abstract public function lintPath($path); abstract public function getLinterName(); public function getLintSeverityMap() { return array(); } public function getLintNameMap() { return array(); } } diff --git a/src/lint/linter/__tests__/ArcanistLinterTestCase.php b/src/lint/linter/__tests__/ArcanistLinterTestCase.php index 364e65f8..2873fb50 100644 --- a/src/lint/linter/__tests__/ArcanistLinterTestCase.php +++ b/src/lint/linter/__tests__/ArcanistLinterTestCase.php @@ -1,175 +1,176 @@ lintFile($root.$file, $linter, $working_copy); } } private function lintFile($file, $linter, $working_copy) { $linter = clone $linter; $contents = Filesystem::readFile($file); $contents = explode("~~~~~~~~~~\n", $contents); if (count($contents) < 2) { throw new Exception( "Expected '~~~~~~~~~~' separating test case and results."); } list ($data, $expect, $xform, $config) = array_merge( $contents, array(null, null)); $basename = basename($file); if ($config) { $config = json_decode($config, true); if (!is_array($config)) { throw new Exception( "Invalid configuration in test '{$basename}', not valid JSON."); } } else { $config = array(); } /* TODO: ? validate_parameter_list( $config, array( ), array( 'project' => true, 'path' => true, 'hook' => true, )); */ $exception = null; $after_lint = null; $messages = null; $exception_message = false; $caught_exception = false; try { $path = idx($config, 'path', 'lint/'.$basename.'.php'); $engine = new UnitTestableArcanistLintEngine(); $engine->setWorkingCopy($working_copy); $engine->setPaths(array($path)); $engine->setCommitHookMode(idx($config, 'hook', false)); $linter->addPath($path); $linter->addData($path, $data); + $linter->setConfig(idx($config, 'config', array())); $engine->addLinter($linter); $engine->addFileData($path, $data); $results = $engine->run(); $this->assertEqual( 1, count($results), 'Expect one result returned by linter.'); $result = reset($results); $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result); $after_lint = $patcher->getModifiedFileContent(); } catch (ArcanistPhutilTestTerminatedException $ex) { throw $ex; } catch (Exception $exception) { $caught_exception = true; if ($exception instanceof PhutilAggregateException) { $caught_exception = false; foreach ($exception->getExceptions() as $ex) { if ($ex instanceof ArcanistUsageException) { $this->assertSkipped($ex->getMessage()); } else { $caught_exception = true; } } } $exception_message = $exception->getMessage()."\n\n". $exception->getTraceAsString(); } switch ($basename) { default: $this->assertEqual(false, $caught_exception, $exception_message); $this->compareLint($basename, $expect, $result); $this->compareTransform($xform, $after_lint); break; } } private function compareLint($file, $expect, $result) { $seen = array(); $raised = array(); $message_map = array(); foreach ($result->getMessages() as $message) { $sev = $message->getSeverity(); $line = $message->getLine(); $char = $message->getChar(); $code = $message->getCode(); $name = $message->getName(); $message_key = $sev.":".$line.":".$char; $message_map[$message_key] = $message; $seen[] = $message_key; $raised[] = " {$sev} at line {$line}, char {$char}: {$code} {$name}"; } $expect = trim($expect); if ($expect) { $expect = explode("\n", $expect); } else { $expect = array(); } foreach ($expect as $key => $expected) { $expect[$key] = head(explode(' ', $expected)); } $expect = array_fill_keys($expect, true); $seen = array_fill_keys($seen, true); if (!$raised) { $raised = array("No messages."); } $raised = "Actually raised:\n".implode("\n", $raised); foreach (array_diff_key($expect, $seen) as $missing => $ignored) { list($sev, $line, $char) = explode(':', $missing); $this->assertFailure( "In '{$file}', ". "expected lint to raise {$sev} on line {$line} at char {$char}, ". "but no {$sev} was raised. {$raised}"); } foreach (array_diff_key($seen, $expect) as $surprising => $ignored) { $message = $message_map[$surprising]; $message_info = $message->getDescription(); list($sev, $line, $char) = explode(':', $surprising); $this->assertFailure( "In '{$file}', ". "lint raised {$sev} on line {$line} at char {$char}, ". "but nothing was expected:\n\n{$message_info}\n\n{$raised}"); } } protected function compareTransform($expected, $actual) { if (!strlen($expected)) { return; } $this->assertEqual( $expected, $actual, "File as patched by lint did not match the expected patched file."); } } diff --git a/src/lint/linter/__tests__/apachelicense/c-basic.lint-test b/src/lint/linter/__tests__/apachelicense/c-basic.lint-test index 1c49d4ae..109eeb62 100644 --- a/src/lint/linter/__tests__/apachelicense/c-basic.lint-test +++ b/src/lint/linter/__tests__/apachelicense/c-basic.lint-test @@ -1,29 +1,31 @@ #include int main(int argv, char **argv) { return 0; } ~~~~~~~~~~ error:1:1 ~~~~~~~~~~ /* * Copyright YYYY Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include int main(int argv, char **argv) { return 0; } +~~~~~~~~~~ +{"config": {"copyright_holder": "Facebook, Inc."}} diff --git a/src/lint/linter/__tests__/apachelicense/greedy.lint-test b/src/lint/linter/__tests__/apachelicense/greedy.lint-test index e111c0bd..33a8a6a7 100644 --- a/src/lint/linter/__tests__/apachelicense/greedy.lint-test +++ b/src/lint/linter/__tests__/apachelicense/greedy.lint-test @@ -1,37 +1,39 @@