diff --git a/src/lint/linter/ArcanistBaseXHPASTLinter.php b/src/lint/linter/ArcanistBaseXHPASTLinter.php index 1fe7d778..86b0072e 100644 --- a/src/lint/linter/ArcanistBaseXHPASTLinter.php +++ b/src/lint/linter/ArcanistBaseXHPASTLinter.php @@ -1,151 +1,151 @@ raiseLintAtOffset( $token->getOffset(), $code, $desc, $token->getValue(), $replace); } - protected final function raiseLintAtNode( + final protected function raiseLintAtNode( XHPASTNode $node, $code, $desc, $replace = null) { return $this->raiseLintAtOffset( $node->getOffset(), $code, $desc, $node->getConcreteString(), $replace); } protected function buildFutures(array $paths) { return $this->getXHPASTLinter()->buildSharedFutures($paths); } /* -( Sharing Parse Trees )------------------------------------------------ */ /** * Get the linter object which is responsible for building parse trees. * * When the engine specifies that several XHPAST linters should execute, * we designate one of them as the one which will actually build parse trees. * The other linters share trees, so they don't have to recompute them. * * Roughly, the first linter to execute elects itself as the builder. * Subsequent linters request builds and retrieve results from it. * * @return ArcanistBaseXHPASTLinter Responsible linter. * @task sharing */ protected function getXHPASTLinter() { $resource_key = 'xhpast.linter'; // If we're the first linter to run, share ourselves. Otherwise, grab the // previously shared linter. $engine = $this->getEngine(); $linter = $engine->getLinterResource($resource_key); if (!$linter) { $linter = $this; $engine->setLinterResource($resource_key, $linter); } $base_class = __CLASS__; if (!($linter instanceof $base_class)) { throw new Exception( pht( 'Expected resource "%s" to be an instance of "%s"!', $resource_key, $base_class)); } return $linter; } /** * Build futures on this linter, for use and to share with other linters. * * @param list Paths to build futures for. * @return list Futures. * @task sharing */ protected function buildSharedFutures(array $paths) { foreach ($paths as $path) { if (!isset($this->futures[$path])) { $this->futures[$path] = xhpast_get_parser_future($this->getData($path)); } } return array_select_keys($this->futures, $paths); } /** * Get a path's tree from the responsible linter. * * @param string Path to retrieve tree for. * @return XHPASTTree|null Tree, or null if unparseable. * @task sharing */ protected function getXHPASTTreeForPath($path) { // If we aren't the linter responsible for actually building the parse // trees, go get the tree from that linter. if ($this->getXHPASTLinter() !== $this) { return $this->getXHPASTLinter()->getXHPASTTreeForPath($path); } if (!array_key_exists($path, $this->trees)) { $this->trees[$path] = null; try { $this->trees[$path] = XHPASTTree::newFromDataAndResolvedExecFuture( $this->getData($path), $this->futures[$path]->resolve()); $root = $this->trees[$path]->getRootNode(); $root->buildSelectCache(); $root->buildTokenCache(); } catch (Exception $ex) { $this->exceptions[$path] = $ex; } } return $this->trees[$path]; } /** * Get a path's parse exception from the responsible linter. * * @param string Path to retrieve exception for. * @return Exeption|null Parse exception, if available. * @task sharing */ protected function getXHPASTExceptionForPath($path) { if ($this->getXHPASTLinter() !== $this) { return $this->getXHPASTLinter()->getXHPASTExceptionForPath($path); } return idx($this->exceptions, $path); } } diff --git a/src/lint/linter/ArcanistExternalLinter.php b/src/lint/linter/ArcanistExternalLinter.php index a1ea0b72..e7a21a9e 100644 --- a/src/lint/linter/ArcanistExternalLinter.php +++ b/src/lint/linter/ArcanistExternalLinter.php @@ -1,550 +1,550 @@ Mandatory flags, like `"--format=xml"`. * @task bin */ protected function getMandatoryFlags() { return array(); } /** * Provide default, overridable flags to the linter. Generally these are * configuration flags which affect behavior but aren't critical. Flags * which are required should be provided in @{method:getMandatoryFlags} * instead. * * Default flags can be overridden with @{method:setFlags}. * * @return list Overridable default flags. * @task bin */ protected function getDefaultFlags() { return array(); } /** * Override default flags with custom flags. If not overridden, flags provided * by @{method:getDefaultFlags} are used. * * @param list New flags. * @return this * @task bin */ final public function setFlags($flags) { $this->flags = (array)$flags; return $this; } /** * Return the binary or script to execute. This method synthesizes defaults * and configuration. You can override the binary with @{method:setBinary}. * * @return string Binary to execute. * @task bin */ final public function getBinary() { return coalesce($this->bin, $this->getDefaultBinary()); } /** * Override the default binary with a new one. * * @param string New binary. * @return this * @task bin */ final public function setBinary($bin) { $this->bin = $bin; return $this; } /** * Return true if this linter should use an interpreter (like "python" or * "node") in addition to the script. * * After overriding this method to return `true`, override * @{method:getDefaultInterpreter} to set a default. * * @return bool True to use an interpreter. * @task bin */ public function shouldUseInterpreter() { return false; } /** * Return the default interpreter, like "python" or "node". This method is * only invoked if @{method:shouldUseInterpreter} has been overridden to * return `true`. * * @return string Default interpreter. * @task bin */ public function getDefaultInterpreter() { throw new Exception("Incomplete implementation!"); } /** * Get the effective interpreter. This method synthesizes configuration and * defaults. * * @return string Effective interpreter. * @task bin */ final public function getInterpreter() { return coalesce($this->interpreter, $this->getDefaultInterpreter()); } /** * Set the interpreter, overriding any default. * * @param string New interpreter. * @return this * @task bin */ final public function setInterpreter($interpreter) { $this->interpreter = $interpreter; return $this; } /* -( Parsing Linter Output )---------------------------------------------- */ /** * Parse the output of the external lint program into objects of class * @{class:ArcanistLintMessage} which `arc` can consume. Generally, this * means examining the output and converting each warning or error into a * message. * * If parsing fails, returning `false` will cause the caller to throw an * appropriate exception. (You can also throw a more specific exception if * you're able to detect a more specific condition.) Otherwise, return a list * of messages. * * @param string Path to the file being linted. * @param int Exit code of the linter. * @param string Stdout of the linter. * @param string Stderr of the linter. * @return list|false List of lint messages, or false * to indicate parser failure. * @task parse */ abstract protected function parseLinterOutput($path, $err, $stdout, $stderr); /* -( Executing the Linter )----------------------------------------------- */ /** * Check that the binary and interpreter (if applicable) exist, and throw * an exception with a message about how to install them if they do not. * * @return void */ final public function checkBinaryConfiguration() { $interpreter = null; if ($this->shouldUseInterpreter()) { $interpreter = $this->getInterpreter(); } $binary = $this->getBinary(); // NOTE: If we have an interpreter, we don't require the script to be // executable (so we just check that the path exists). Otherwise, the // binary must be executable. if ($interpreter) { if (!Filesystem::binaryExists($interpreter)) { throw new ArcanistUsageException( pht( 'Unable to locate interpreter "%s" to run linter %s. You may '. 'need to install the intepreter, or adjust your linter '. 'configuration.'. "\nTO INSTALL: %s", $interpreter, get_class($this), $this->getInstallInstructions())); } if (!Filesystem::pathExists($binary)) { throw new ArcanistUsageException( pht( 'Unable to locate script "%s" to run linter %s. You may need '. 'to install the script, or adjust your linter configuration. '. "\nTO INSTALL: %s", $binary, get_class($this), $this->getInstallInstructions())); } } else { if (!Filesystem::binaryExists($binary)) { throw new ArcanistUsageException( pht( 'Unable to locate binary "%s" to run linter %s. You may need '. 'to install the binary, or adjust your linter configuration. '. "\nTO INSTALL: %s", $binary, get_class($this), $this->getInstallInstructions())); } } } /** * Get the composed executable command, including the interpreter and binary * but without flags or paths. This can be used to execute `--version` * commands. * * @return string Command to execute the raw linter. * @task exec */ - protected function getExecutableCommand() { + final protected function getExecutableCommand() { $this->checkBinaryConfiguration(); $interpreter = null; if ($this->shouldUseInterpreter()) { $interpreter = $this->getInterpreter(); } $binary = $this->getBinary(); if ($interpreter) { $bin = csprintf('%s %s', $interpreter, $binary); } else { $bin = csprintf('%s', $binary); } return $bin; } /** * Get the composed flags for the executable, including both mandatory and * configured flags. * * @return list Composed flags. * @task exec */ - protected function getCommandFlags() { + final protected function getCommandFlags() { $mandatory_flags = $this->getMandatoryFlags(); if (!is_array($mandatory_flags)) { phutil_deprecated( 'String support for flags.', 'You should use list instead.'); $mandatory_flags = (array) $mandatory_flags; } $flags = nonempty($this->flags, $this->getDefaultFlags()); if (!is_array($flags)) { phutil_deprecated( 'String support for flags.', 'You should use list instead.'); $flags = (array) $flags; } return array_merge($mandatory_flags, $flags); } public function getCacheVersion() { $version = $this->getVersion(); if ($version) { return $version.'-'.json_encode($this->getCommandFlags()); } else { // Either we failed to parse the version number or the `getVersion` // function hasn't been implemented. return json_encode($this->getCommandFlags()); } } /** * Prepare the path to be added to the command string. * * This method is expected to return an already escaped string. * * @param string Path to the file being linted * @return string The command-ready file argument */ protected function getPathArgumentForLinterFuture($path) { return csprintf('%s', $path); } - protected function buildFutures(array $paths) { + final protected function buildFutures(array $paths) { $executable = $this->getExecutableCommand(); $bin = csprintf('%C %Ls', $executable, $this->getCommandFlags()); $futures = array(); foreach ($paths as $path) { if ($this->supportsReadDataFromStdin()) { $future = new ExecFuture( '%C %C', $bin, $this->getReadDataFromStdinFilename()); $future->write($this->getEngine()->loadData($path)); } else { // TODO: In commit hook mode, we need to do more handling here. $disk_path = $this->getEngine()->getFilePathOnDisk($path); $path_argument = $this->getPathArgumentForLinterFuture($disk_path); $future = new ExecFuture('%C %C', $bin, $path_argument); } $futures[$path] = $future; } return $futures; } - protected function resolveFuture($path, Future $future) { + final protected function resolveFuture($path, Future $future) { list($err, $stdout, $stderr) = $future->resolve(); if ($err && !$this->shouldExpectCommandErrors()) { $future->resolvex(); } $messages = $this->parseLinterOutput($path, $err, $stdout, $stderr); if ($messages === false) { if ($err) { $future->resolvex(); } else { throw new Exception( "Linter failed to parse output!\n\n{$stdout}\n\n{$stderr}"); } } foreach ($messages as $message) { $this->addLintMessage($message); } } public function getLinterConfigurationOptions() { $options = array( 'bin' => array( 'type' => 'optional string | list', 'help' => pht( 'Specify a string (or list of strings) identifying the binary '. 'which should be invoked to execute this linter. This overrides '. 'the default binary. If you provide a list of possible binaries, '. 'the first one which exists will be used.') ), 'flags' => array( 'type' => 'optional list', 'help' => pht( 'Provide a list of additional flags to pass to the linter on the '. 'command line.'), ), ); if ($this->shouldUseInterpreter()) { $options['interpreter'] = array( 'type' => 'optional string | list', 'help' => pht( 'Specify a string (or list of strings) identifying the interpreter '. 'which should be used to invoke the linter binary. If you provide '. 'a list of possible interpreters, the first one that exists '. 'will be used.'), ); } return $options + parent::getLinterConfigurationOptions(); } public function setLinterConfigurationValue($key, $value) { switch ($key) { case 'interpreter': $working_copy = $this->getEngine()->getWorkingCopy(); $root = $working_copy->getProjectRoot(); foreach ((array)$value as $path) { if (Filesystem::binaryExists($path)) { $this->setInterpreter($path); return; } $path = Filesystem::resolvePath($path, $root); if (Filesystem::binaryExists($path)) { $this->setInterpreter($path); return; } } throw new Exception( pht('None of the configured interpreters can be located.')); case 'bin': $is_script = $this->shouldUseInterpreter(); $working_copy = $this->getEngine()->getWorkingCopy(); $root = $working_copy->getProjectRoot(); foreach ((array)$value as $path) { if (!$is_script && Filesystem::binaryExists($path)) { $this->setBinary($path); return; } $path = Filesystem::resolvePath($path, $root); if ((!$is_script && Filesystem::binaryExists($path)) || ($is_script && Filesystem::pathExists($path))) { $this->setBinary($path); return; } } throw new Exception( pht('None of the configured binaries can be located.')); case 'flags': if (!is_array($value)) { phutil_deprecated( 'String support for flags.', 'You should use list instead.'); $value = (array) $value; } $this->setFlags($value); return; } return parent::setLinterConfigurationValue($key, $value); } /** * Map a configuration lint code to an `arc` lint code. Primarily, this is * intended for validation, but can also be used to normalize case or * otherwise be more permissive in accepted inputs. * * If the code is not recognized, you should throw an exception. * * @param string Code specified in configuration. * @return string Normalized code to use in severity map. */ protected function getLintCodeFromLinterConfigurationKey($code) { return $code; } } diff --git a/src/lint/linter/ArcanistFutureLinter.php b/src/lint/linter/ArcanistFutureLinter.php index 7b5fb775..30105c70 100644 --- a/src/lint/linter/ArcanistFutureLinter.php +++ b/src/lint/linter/ArcanistFutureLinter.php @@ -1,34 +1,33 @@ getFuturesLimit(); $this->futures = Futures(array())->limit($limit); foreach ($this->buildFutures($paths) as $path => $future) { $this->futures->addFuture($future, $path); } } - public function lintPath($path) { - } + final public function lintPath($path) {} - public function didRunLinters() { + final public function didRunLinters() { if ($this->futures) { foreach ($this->futures as $path => $future) { $this->willLintPath($path); $this->resolveFuture($path, $future); } } } } diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php index f599505a..b6b566e2 100644 --- a/src/lint/linter/ArcanistLinter.php +++ b/src/lint/linter/ArcanistLinter.php @@ -1,504 +1,504 @@ getLinterName(), $this->getLinterConfigurationName(), get_class($this)); } public function getLinterPriority() { return 1.0; } public function setCustomSeverityMap(array $map) { $this->customSeverityMap = $map; return $this; } public function setCustomSeverityRules(array $rules) { $this->customSeverityRules = $rules; return $this; } - public function getActivePath() { + final public function getActivePath() { return $this->activePath; } - public function getOtherLocation($offset, $path = null) { + final public function getOtherLocation($offset, $path = null) { if ($path === null) { $path = $this->getActivePath(); } list($line, $char) = $this->getEngine()->getLineAndCharFromOffset( $path, $offset); return array( 'path' => $path, 'line' => $line + 1, 'char' => $char, ); } - public function stopAllLinters() { + final public function stopAllLinters() { $this->stopAllLinters = true; return $this; } - public function didStopAllLinters() { + final public function didStopAllLinters() { return $this->stopAllLinters; } - public function addPath($path) { + final public function addPath($path) { $this->paths[$path] = $path; return $this; } - public function setPaths(array $paths) { + final public function setPaths(array $paths) { $this->paths = $paths; return $this; } /** * Filter out paths which this linter doesn't act on (for example, because * they are binaries and the linter doesn't apply to binaries). */ - private function filterPaths($paths) { + final private function filterPaths($paths) { $engine = $this->getEngine(); $keep = array(); foreach ($paths as $path) { if (!$this->shouldLintDeletedFiles() && !$engine->pathExists($path)) { continue; } if (!$this->shouldLintDirectories() && $engine->isDirectory($path)) { continue; } if (!$this->shouldLintBinaryFiles() && $engine->isBinaryFile($path)) { continue; } $keep[] = $path; } return $keep; } - public function getPaths() { + final public function getPaths() { return $this->filterPaths(array_values($this->paths)); } - public function addData($path, $data) { + final public function addData($path, $data) { $this->data[$path] = $data; return $this; } - protected function getData($path) { + final 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() { + final protected function getEngine() { return $this->engine; } public function getCacheVersion() { return 0; } - public function getLintMessageFullCode($short_code) { + final public function getLintMessageFullCode($short_code) { return $this->getLinterName().$short_code; } - public function getLintMessageSeverity($code) { + final public function getLintMessageSeverity($code) { $map = $this->customSeverityMap; if (isset($map[$code])) { return $map[$code]; } $map = $this->getLintSeverityMap(); if (isset($map[$code])) { return $map[$code]; } foreach ($this->customSeverityRules as $rule => $severity) { if (preg_match($rule, $code)) { return $severity; } } return $this->getDefaultMessageSeverity($code); } protected function getDefaultMessageSeverity($code) { return ArcanistLintSeverity::SEVERITY_ERROR; } - public function isMessageEnabled($code) { + final public function isMessageEnabled($code) { return ($this->getLintMessageSeverity($code) !== ArcanistLintSeverity::SEVERITY_DISABLED); } - public function getLintMessageName($code) { + final public function getLintMessageName($code) { $map = $this->getLintNameMap(); if (isset($map[$code])) { return $map[$code]; } return "Unknown lint message!"; } - protected function addLintMessage(ArcanistLintMessage $message) { + final 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() { + final public function getLintMessages() { return $this->messages; } - protected function raiseLintAtLine( + final protected function raiseLintAtLine( $line, $char, $code, $desc, $original = null, $replacement = null) { $message = id(new ArcanistLintMessage()) ->setPath($this->getActivePath()) ->setLine($line) ->setChar($char) ->setCode($this->getLintMessageFullCode($code)) ->setSeverity($this->getLintMessageSeverity($code)) ->setName($this->getLintMessageName($code)) ->setDescription($desc) ->setOriginalText($original) ->setReplacementText($replacement); return $this->addLintMessage($message); } - protected function raiseLintAtPath( + final protected function raiseLintAtPath( $code, $desc) { return $this->raiseLintAtLine(null, null, $code, $desc, null, null); } - protected function raiseLintAtOffset( + final 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; } public function willLintPaths(array $paths) { return; } abstract public function lintPath($path); abstract public function getLinterName(); public function getVersion() { return null; } public function didRunLinters() { // This is a hook. } - protected function isCodeEnabled($code) { + final protected function isCodeEnabled($code) { $severity = $this->getLintMessageSeverity($code); return $this->getEngine()->isSeverityEnabled($severity); } public function getLintSeverityMap() { return array(); } public function getLintNameMap() { return array(); } public function getCacheGranularity() { return self::GRANULARITY_FILE; } /** * If this linter is selectable via `.arclint` configuration files, return * a short, human-readable name to identify it. For example, `"jshint"` or * `"pep8"`. * * If you do not implement this method, the linter will not be selectable * through `.arclint` files. */ public function getLinterConfigurationName() { return null; } public function getLinterConfigurationOptions() { if (!$this->canCustomizeLintSeverities()) { return array(); } return array( 'severity' => array( 'type' => 'optional map', 'help' => pht( 'Provide a map from lint codes to adjusted severity levels: error, '. 'warning, advice, autofix or disabled.') ), 'severity.rules' => array( 'type' => 'optional map', 'help' => pht( 'Provide a map of regular expressions to severity levels. All '. 'matching codes have their severity adjusted.'), ), ); } public function setLinterConfigurationValue($key, $value) { $sev_map = array( 'error' => ArcanistLintSeverity::SEVERITY_ERROR, 'warning' => ArcanistLintSeverity::SEVERITY_WARNING, 'autofix' => ArcanistLintSeverity::SEVERITY_AUTOFIX, 'advice' => ArcanistLintSeverity::SEVERITY_ADVICE, 'disabled' => ArcanistLintSeverity::SEVERITY_DISABLED, ); switch ($key) { case 'severity': if (!$this->canCustomizeLintSeverities()) { break; } $custom = array(); foreach ($value as $code => $severity) { if (empty($sev_map[$severity])) { $valid = implode(', ', array_keys($sev_map)); throw new Exception( pht( 'Unknown lint severity "%s". Valid severities are: %s.', $severity, $valid)); } $code = $this->getLintCodeFromLinterConfigurationKey($code); $custom[$code] = $severity; } $this->setCustomSeverityMap($custom); return; case 'severity.rules': if (!$this->canCustomizeLintSeverities()) { break; } foreach ($value as $rule => $severity) { if (@preg_match($rule, '') === false) { throw new Exception( pht( 'Severity rule "%s" is not a valid regular expression.', $rule)); } if (empty($sev_map[$severity])) { $valid = implode(', ', array_keys($sev_map)); throw new Exception( pht( 'Unknown lint severity "%s". Valid severities are: %s.', $severity, $valid)); } } $this->setCustomSeverityRules($value); return; } throw new Exception("Incomplete implementation: {$key}!"); } protected function canCustomizeLintSeverities() { return true; } protected function shouldLintBinaryFiles() { return false; } protected function shouldLintDeletedFiles() { return false; } protected function shouldLintDirectories() { return false; } /** * Map a configuration lint code to an `arc` lint code. Primarily, this is * intended for validation, but can also be used to normalize case or * otherwise be more permissive in accepted inputs. * * If the code is not recognized, you should throw an exception. * * @param string Code specified in configuration. * @return string Normalized code to use in severity map. */ protected function getLintCodeFromLinterConfigurationKey($code) { return $code; } /** * Retrieve an old lint configuration value from `.arcconfig` or a similar * source. * * Modern linters should use @{method:getConfig} to read configuration from * `.arclint`. * * @param string Configuration key to retrieve. * @param wild Default value to return if key is not present in config. * @return wild Configured value, or default if no configuration exists. */ protected function getDeprecatedConfiguration($key, $default = null) { // If we're being called in a context without an engine (probably from // `arc linters`), just return the default value. if (!$this->engine) { return $default; } $config = $this->getEngine()->getConfigurationManager(); // Construct a sentinel object so we can tell if we're reading config // or not. $sentinel = (object)array(); $result = $config->getConfigFromAnySource($key, $sentinel); // If we read config, warn the user that this mechanism is deprecated and // discouraged. if ($result !== $sentinel) { $console = PhutilConsole::getConsole(); $console->writeErr( "**%s**: %s\n", pht('Deprecation Warning'), pht( 'Configuration option "%s" is deprecated. Generally, linters should '. 'now be configured using an `.arclint` file. See "Arcanist User '. 'Guide: Lint" in the documentation for more information.', $key)); return $result; } return $default; } } diff --git a/src/lint/linter/ArcanistXMLLinter.php b/src/lint/linter/ArcanistXMLLinter.php index 59e05feb..f67ef0b7 100644 --- a/src/lint/linter/ArcanistXMLLinter.php +++ b/src/lint/linter/ArcanistXMLLinter.php @@ -1,77 +1,73 @@ getData($path))) { // XML appears to be valid. return; } foreach (libxml_get_errors() as $error) { $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($error->line); $message->setChar($error->column ? $error->column : null); $message->setCode($this->getLintMessageFullCode($error->code)); - $message->setName($this->getLintMessageName($error->code)); + $message->setName('LibXML Error'); $message->setDescription(trim($error->message)); switch ($error->level) { case LIBXML_ERR_NONE: $message->setSeverity(ArcanistLintSeverity::SEVERITY_DISABLED); break; case LIBXML_ERR_WARNING: $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); break; case LIBXML_ERR_ERROR: case LIBXML_ERR_FATAL: $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); break; default: $message->setSeverity(ArcanistLintSeverity::SEVERITY_ADVICE); break; } $this->addLintMessage($message); } } }