Changeset View
Changeset View
Standalone View
Standalone View
src/lint/linter/ArcanistPhutilLibraryLinter.php
| <?php | <?php | ||||
| /** | /** | ||||
| * Applies lint rules for Phutil libraries. We enforce three rules: | * Applies lint rules for Phutil libraries. We enforce three rules: | ||||
| * | * | ||||
| * # If you use a symbol, it must be defined somewhere. | * # If you use a symbol, it must be defined somewhere. | ||||
| * # If you define a symbol, it must not duplicate another definition. | * # If you define a symbol, it must not duplicate another definition. | ||||
| * # If you define a class or interface in a file, it MUST be the only symbol | * # If you define a class or interface in a file, it MUST be the only symbol | ||||
| * defined in that file. | * defined in that file. | ||||
| */ | */ | ||||
| final class ArcanistPhutilLibraryLinter extends ArcanistLinter { | final class ArcanistPhutilLibraryLinter extends ArcanistLinter { | ||||
| const LINT_UNKNOWN_SYMBOL = 1; | const LINT_UNKNOWN_SYMBOL = 1; | ||||
| const LINT_DUPLICATE_SYMBOL = 2; | const LINT_DUPLICATE_SYMBOL = 2; | ||||
| const LINT_ONE_CLASS_PER_FILE = 3; | const LINT_ONE_CLASS_PER_FILE = 3; | ||||
| const LINT_NONCANONICAL_SYMBOL = 4; | |||||
| public function getInfoName() { | public function getInfoName() { | ||||
| return pht('Phutil Library Linter'); | return pht('Phutil Library Linter'); | ||||
| } | } | ||||
| public function getInfoDescription() { | public function getInfoDescription() { | ||||
| return pht( | return pht( | ||||
| 'Make sure all the symbols used in a %s library are defined and known. '. | 'Make sure all the symbols used in a %s library are defined and known. '. | ||||
| Show All 14 Lines | public function getCacheGranularity() { | ||||
| return self::GRANULARITY_GLOBAL; | return self::GRANULARITY_GLOBAL; | ||||
| } | } | ||||
| public function getLintNameMap() { | public function getLintNameMap() { | ||||
| return array( | return array( | ||||
| self::LINT_UNKNOWN_SYMBOL => pht('Unknown Symbol'), | self::LINT_UNKNOWN_SYMBOL => pht('Unknown Symbol'), | ||||
| self::LINT_DUPLICATE_SYMBOL => pht('Duplicate Symbol'), | self::LINT_DUPLICATE_SYMBOL => pht('Duplicate Symbol'), | ||||
| self::LINT_ONE_CLASS_PER_FILE => pht('One Class Per File'), | self::LINT_ONE_CLASS_PER_FILE => pht('One Class Per File'), | ||||
| self::LINT_NONCANONICAL_SYMBOL => pht('Noncanonical Symbol'), | |||||
| ); | ); | ||||
| } | } | ||||
| public function getLinterPriority() { | public function getLinterPriority() { | ||||
| return 2.0; | return 2.0; | ||||
| } | } | ||||
| public function willLintPaths(array $paths) { | public function willLintPaths(array $paths) { | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | foreach ($symbols as $library => $map) { | ||||
| "File '%s' declares more than one class or interface (%s). ". | "File '%s' declares more than one class or interface (%s). ". | ||||
| "A file which declares a class or interface MUST declare ". | "A file which declares a class or interface MUST declare ". | ||||
| "nothing else.", | "nothing else.", | ||||
| $file, | $file, | ||||
| $class_list)); | $class_list)); | ||||
| } | } | ||||
| } | } | ||||
| $libtype_map = array( | |||||
| 'class' => 'class', | |||||
| 'function' => 'function', | |||||
| 'interface' => 'class', | |||||
| 'class/interface' => 'class', | |||||
| ); | |||||
| // Check for duplicate symbols: two files providing the same class or | // Check for duplicate symbols: two files providing the same class or | ||||
| // function. | // function. While doing this, we also build a map of normalized symbol | ||||
| // names to original symbol names: we want a definition of "idx()" to | |||||
| // collide with a definition of "IdX()", and want to perform spelling | |||||
| // corrections later. | |||||
| $normal_symbols = array(); | |||||
| foreach ($map as $file => $spec) { | foreach ($map as $file => $spec) { | ||||
| $have = idx($spec, 'have', array()); | $have = idx($spec, 'have', array()); | ||||
| foreach (array('class', 'function', 'interface') as $type) { | foreach (array('class', 'function', 'interface') as $type) { | ||||
| $libtype = ($type == 'interface') ? 'class' : $type; | $libtype = $libtype_map[$type]; | ||||
| foreach (idx($have, $type, array()) as $symbol => $offset) { | foreach (idx($have, $type, array()) as $symbol => $offset) { | ||||
| if (empty($all_symbols[$libtype][$symbol])) { | $normal_symbol = $this->normalizeSymbol($symbol); | ||||
| if (empty($normal_symbols[$libtype][$normal_symbol])) { | |||||
| $normal_symbols[$libtype][$normal_symbol] = $symbol; | |||||
| $all_symbols[$libtype][$symbol] = array( | $all_symbols[$libtype][$symbol] = array( | ||||
| 'library' => $library, | 'library' => $library, | ||||
| 'file' => $file, | 'file' => $file, | ||||
| 'offset' => $offset, | 'offset' => $offset, | ||||
| ); | ); | ||||
| continue; | continue; | ||||
| } | } | ||||
| $osrc = $all_symbols[$libtype][$symbol]['file']; | $old_symbol = $normal_symbols[$libtype][$normal_symbol]; | ||||
| $olib = $all_symbols[$libtype][$symbol]['library']; | $old_src = $all_symbols[$libtype][$old_symbol]['file']; | ||||
| $old_lib = $all_symbols[$libtype][$old_symbol]['library']; | |||||
| $this->raiseLintInLibrary( | $this->raiseLintInLibrary( | ||||
| $library, | $library, | ||||
| $file, | $file, | ||||
| $offset, | $offset, | ||||
| self::LINT_DUPLICATE_SYMBOL, | self::LINT_DUPLICATE_SYMBOL, | ||||
| pht( | pht( | ||||
| "Definition of %s '%s' in '%s' in library '%s' duplicates ". | 'Definition of symbol "%s" (of type "%s") in file "%s" in '. | ||||
| "prior definition in '%s' in library '%s'.", | 'library "%s" duplicates prior definition in file "%s" in '. | ||||
| $type, | 'library "%s".', | ||||
| $symbol, | $symbol, | ||||
| $type, | |||||
| $file, | $file, | ||||
| $library, | $library, | ||||
| $osrc, | $old_src, | ||||
| $olib)); | $old_lib)); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| $types = array('class', 'function', 'interface', 'class/interface'); | $types = array('class', 'function', 'interface', 'class/interface'); | ||||
| foreach ($symbols as $library => $map) { | foreach ($symbols as $library => $map) { | ||||
| // Check for unknown symbols: uses of classes, functions or interfaces | // Check for unknown symbols: uses of classes, functions or interfaces | ||||
| // which are not defined anywhere. We reference the list of all symbols | // which are not defined anywhere. We reference the list of all symbols | ||||
| // we built up earlier. | // we built up earlier. | ||||
| foreach ($map as $file => $spec) { | foreach ($map as $file => $spec) { | ||||
| $need = idx($spec, 'need', array()); | $need = idx($spec, 'need', array()); | ||||
| foreach ($types as $type) { | foreach ($types as $type) { | ||||
| $libtype = $type; | $libtype = $libtype_map[$type]; | ||||
| if ($type == 'interface' || $type == 'class/interface') { | |||||
| $libtype = 'class'; | |||||
| } | |||||
| foreach (idx($need, $type, array()) as $symbol => $offset) { | foreach (idx($need, $type, array()) as $symbol => $offset) { | ||||
| if (!empty($all_symbols[$libtype][$symbol])) { | if (!empty($all_symbols[$libtype][$symbol])) { | ||||
| // Symbol is defined somewhere. | // Symbol is defined somewhere. | ||||
| continue; | continue; | ||||
| } | } | ||||
| $libphutil_root = dirname(phutil_get_library_root('arcanist')); | $normal_symbol = $this->normalizeSymbol($symbol); | ||||
| if (!empty($normal_symbols[$libtype][$normal_symbol])) { | |||||
| $proper_symbol = $normal_symbols[$libtype][$normal_symbol]; | |||||
| switch ($type) { | |||||
| case 'class': | |||||
| $summary = pht( | |||||
| 'Class symbol "%s" should be written as "%s".', | |||||
| $symbol, | |||||
| $proper_symbol); | |||||
| break; | |||||
| case 'function': | |||||
| $summary = pht( | |||||
| 'Function symbol "%s" should be written as "%s".', | |||||
| $symbol, | |||||
| $proper_symbol); | |||||
| break; | |||||
| case 'interface': | |||||
| $summary = pht( | |||||
| 'Interface symbol "%s" should be written as "%s".', | |||||
| $symbol, | |||||
| $proper_symbol); | |||||
| break; | |||||
| case 'class/interface': | |||||
| $summary = pht( | |||||
| 'Class or interface symbol "%s" should be written as "%s".', | |||||
| $symbol, | |||||
| $proper_symbol); | |||||
| break; | |||||
| default: | |||||
| throw new Exception( | |||||
| pht('Unknown symbol type "%s".', $type)); | |||||
| } | |||||
| $this->raiseLintInLibrary( | $this->raiseLintInLibrary( | ||||
| $library, | $library, | ||||
| $file, | $file, | ||||
| $offset, | $offset, | ||||
| self::LINT_UNKNOWN_SYMBOL, | self::LINT_NONCANONICAL_SYMBOL, | ||||
| pht( | $summary, | ||||
| "Use of unknown %s '%s'. Common causes are:\n\n". | $symbol, | ||||
| " - Your %s is out of date.\n". | $proper_symbol); | ||||
| continue; | |||||
| } | |||||
| $arcanist_root = dirname(phutil_get_library_root('arcanist')); | |||||
| switch ($type) { | |||||
| case 'class': | |||||
| $summary = pht( | |||||
| 'Use of unknown class symbol "%s".', | |||||
| $symbol); | |||||
| break; | |||||
| case 'function': | |||||
| $summary = pht( | |||||
| 'Use of unknown function symbol "%s".', | |||||
| $symbol); | |||||
| break; | |||||
| case 'interface': | |||||
| $summary = pht( | |||||
| 'Use of unknown interface symbol "%s".', | |||||
| $symbol); | |||||
| break; | |||||
| case 'class/interface': | |||||
| $summary = pht( | |||||
| 'Use of unknown class or interface symbol "%s".', | |||||
| $symbol); | |||||
| break; | |||||
| } | |||||
| $details = pht( | |||||
| "Common causes are:\n". | |||||
| "\n". | |||||
| " - Your copy of Arcanist is out of date.\n". | |||||
| " This is the most common cause.\n". | " This is the most common cause.\n". | ||||
| " Update this copy of libphutil: %s\n\n". | " Update this copy of Arcanist:\n". | ||||
| "\n". | |||||
| " %s\n". | |||||
| "\n". | |||||
| " - Some other library is out of date.\n". | " - Some other library is out of date.\n". | ||||
| " Update the library this symbol appears in.\n\n". | " Update the library this symbol appears in.\n". | ||||
| " - This symbol is misspelled.\n". | "\n". | ||||
| " - The symbol is misspelled.\n". | |||||
| " Spell the symbol name correctly.\n". | " Spell the symbol name correctly.\n". | ||||
| " Symbol name spelling is case-sensitive.\n\n". | "\n". | ||||
| " - This symbol was added recently.\n". | " - You added the symbol recently, but have not updated\n". | ||||
| " Run `%s` on the library it was added to.\n\n". | " the symbol map for the library.\n". | ||||
| " - This symbol is external. Use `%s`.\n". | " Run \"arc liberate\" in the library where the symbol is\n". | ||||
| " Use `%s` to find usage examples of this directive.\n\n". | " defined.\n". | ||||
| "*** ALTHOUGH USUALLY EASY TO FIX, THIS IS A SERIOUS ERROR.\n". | "\n". | ||||
| "*** THIS ERROR IS YOUR FAULT. YOU MUST RESOLVE IT.", | " - This symbol is defined in an external library.\n". | ||||
| $type, | " Use \"@phutil-external-symbol\" to annotate it.\n". | ||||
| $symbol, | " Use \"grep\" to find examples of usage.", | ||||
| 'libphutil/', | $arcanist_root); | ||||
| $libphutil_root, | |||||
| 'arc liberate', | $message = implode( | ||||
| '@phutil-external-symbol', | "\n\n", | ||||
| 'grep')); | array( | ||||
| $summary, | |||||
| $details, | |||||
| )); | |||||
| $this->raiseLintInLibrary( | |||||
| $library, | |||||
| $file, | |||||
| $offset, | |||||
| self::LINT_UNKNOWN_SYMBOL, | |||||
| $message); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private function raiseLintInLibrary($library, $path, $offset, $code, $desc) { | private function raiseLintInLibrary( | ||||
| $library, | |||||
| $path, | |||||
| $offset, | |||||
| $code, | |||||
| $desc, | |||||
| $original = null, | |||||
| $replacement = null) { | |||||
| $root = phutil_get_library_root($library); | $root = phutil_get_library_root($library); | ||||
| $this->activePath = $root.'/'.$path; | $this->activePath = $root.'/'.$path; | ||||
| $this->raiseLintAtOffset($offset, $code, $desc); | $this->raiseLintAtOffset($offset, $code, $desc, $original, $replacement); | ||||
| } | } | ||||
| public function lintPath($path) { | public function lintPath($path) { | ||||
| return; | return; | ||||
| } | } | ||||
| private function normalizeSymbol($symbol) { | |||||
| return phutil_utf8_strtolower($symbol); | |||||
| } | |||||
| } | } | ||||