mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-21 22:32:41 +01:00
Add "ArcanistPhutilLibraryLinter" to replace "ArcanistPhutilModuleLinter"
Summary: Adds a linter for v2 libraries which raises the relevant errors. NOTE: Not hooked up anywhere yet, so this diff has no effect. Test Plan: Switched the ModuleLinter to LibraryLinter and ran it with a junk block to trigger errors: >>> Lint for src/lint/linter/phutillibrary/ArcanistPhutilLibraryLinter.php: Error (PHL3) One Class Per File File 'lint/linter/phutillibrary/ArcanistPhutilLibraryLinter.php' mixes function (id) and class/interface (ArcanistPhutilLibraryLinter) definitions in the same file. A file which declares a class or an interface MUST declare nothing else. 190 } 191 192 if (false) { 193 function id() { } 194 new XYZ(); 195 } Error (PHL2) Duplicate Symbol Definition of function 'id' in 'lint/linter/phutillibrary/ArcanistPhutilLibraryLinter.php' in library 'arcanist' duplicates prior definition in 'utils/utils.php' in library 'phutil'. 190 } 191 192 if (false) { 193 function id() { } 194 new XYZ(); 195 } Error (PHL1) Unknown Symbol Use of unknown class 'XYZ'. This symbol is not defined in any loaded libphutil library. 191 192 if (false) { 193 function id() { } 194 new XYZ(); 195 } Reviewers: vrana, btrahan Reviewed By: vrana CC: aran Maniphest Tasks: T1103 Differential Revision: https://secure.phabricator.com/D2597
This commit is contained in:
parent
61b0cfac63
commit
009e6c4dbf
4 changed files with 273 additions and 4 deletions
|
@ -47,6 +47,15 @@ $args->parse(
|
|||
'help' => 'Controls the number of symbol mapper subprocesses run '.
|
||||
'at once. Defaults to 8.',
|
||||
),
|
||||
array(
|
||||
'name' => 'show',
|
||||
'help' => 'Print symbol map to stdout instead of writing it to the '.
|
||||
'map file.',
|
||||
),
|
||||
array(
|
||||
'name' => 'ugly',
|
||||
'help' => 'Use faster but less readable serialization for --show.',
|
||||
),
|
||||
array(
|
||||
'name' => 'root',
|
||||
'wildcard' => true,
|
||||
|
@ -68,6 +77,11 @@ if ($args->getArg('drop-cache')) {
|
|||
$builder->dropSymbolCache();
|
||||
}
|
||||
|
||||
if ($args->getArg('show')) {
|
||||
$builder->setShowMap(true);
|
||||
$builder->setUgly($args->getArg('ugly'));
|
||||
}
|
||||
|
||||
$builder->buildMap();
|
||||
|
||||
exit(0);
|
||||
|
@ -89,6 +103,8 @@ final class PhutilLibraryMapBuilder {
|
|||
private $root;
|
||||
private $quiet;
|
||||
private $subprocessLimit = 8;
|
||||
private $ugly;
|
||||
private $showMap;
|
||||
|
||||
const LIBRARY_MAP_VERSION_KEY = '__library_version__';
|
||||
const LIBRARY_MAP_VERSION = 2;
|
||||
|
@ -140,6 +156,36 @@ final class PhutilLibraryMapBuilder {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Control whether the ugly (but fast) or pretty (but slower) JSON formatter
|
||||
* is used.
|
||||
*
|
||||
* @param bool If true, use the fastest formatter.
|
||||
* @return this
|
||||
*
|
||||
* @task map
|
||||
*/
|
||||
public function setUgly($ugly) {
|
||||
$this->ugly = $ugly;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Control whether the map should be rebuilt, or just shown (printed to
|
||||
* stdout in JSON).
|
||||
*
|
||||
* @param bool If true, show map instead of updating.
|
||||
* @return this
|
||||
*
|
||||
* @task map
|
||||
*/
|
||||
public function setShowMap($show_map) {
|
||||
$this->showMap = $show_map;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build or rebuild the library map.
|
||||
*
|
||||
|
@ -196,11 +242,25 @@ final class PhutilLibraryMapBuilder {
|
|||
// out old cache entries.
|
||||
$this->writeSymbolCache($symbol_map, $source_map);
|
||||
|
||||
$this->log("Building library map...\n");
|
||||
$library_map = $this->buildLibraryMap($symbol_map, $source_map);
|
||||
|
||||
$this->log("Writing map...\n");
|
||||
$this->writeLibraryMap($library_map);
|
||||
// Our map is up to date, so either show it on stdout or write it to disk.
|
||||
|
||||
if ($this->showMap) {
|
||||
$this->log("Showing map...\n");
|
||||
|
||||
if ($this->ugly) {
|
||||
echo json_encode($symbol_map);
|
||||
} else {
|
||||
$json = new PhutilJSON();
|
||||
echo $json->encodeFormatted($symbol_map);
|
||||
}
|
||||
} else {
|
||||
$this->log("Building library map...\n");
|
||||
$library_map = $this->buildLibraryMap($symbol_map, $source_map);
|
||||
|
||||
$this->log("Writing map...\n");
|
||||
$this->writeLibraryMap($library_map);
|
||||
}
|
||||
|
||||
$this->log("Done.\n");
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistPasteWorkflow' => 'workflow/paste',
|
||||
'ArcanistPatchWorkflow' => 'workflow/patch',
|
||||
'ArcanistPhpcsLinter' => 'lint/linter/phpcs',
|
||||
'ArcanistPhutilLibraryLinter' => 'lint/linter/phutillibrary',
|
||||
'ArcanistPhutilModuleLinter' => 'lint/linter/phutilmodule',
|
||||
'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase',
|
||||
'ArcanistPhutilTestSkippedException' => 'unit/engine/phutil/testcase/exception',
|
||||
|
@ -174,6 +175,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistPhpcsLinter' => 'ArcanistLinter',
|
||||
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
|
||||
'ArcanistPhutilModuleLinter' => 'ArcanistLinter',
|
||||
'ArcanistPyFlakesLinter' => 'ArcanistLinter',
|
||||
'ArcanistPyLintLinter' => 'ArcanistLinter',
|
||||
|
|
190
src/lint/linter/phutillibrary/ArcanistPhutilLibraryLinter.php
Normal file
190
src/lint/linter/phutillibrary/ArcanistPhutilLibraryLinter.php
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Applies lint rules for Phutil libraries. We enforce three rules:
|
||||
*
|
||||
* # 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 class or interface in a file, it MUST be the only symbol
|
||||
* defined in that file.
|
||||
*
|
||||
* @group linter
|
||||
*/
|
||||
final class ArcanistPhutilLibraryLinter extends ArcanistLinter {
|
||||
|
||||
const LINT_UNKNOWN_SYMBOL = 1;
|
||||
const LINT_DUPLICATE_SYMBOL = 2;
|
||||
const LINT_ONE_CLASS_PER_FILE = 3;
|
||||
|
||||
public function getLintNameMap() {
|
||||
return array(
|
||||
self::LINT_UNKNOWN_SYMBOL => 'Unknown Symbol',
|
||||
self::LINT_DUPLICATE_SYMBOL => 'Duplicate Symbol',
|
||||
self::LINT_ONE_CLASS_PER_FILE => 'One Class Per File',
|
||||
);
|
||||
}
|
||||
|
||||
public function getLinterName() {
|
||||
return 'PHL';
|
||||
}
|
||||
|
||||
public function getLintSeverityMap() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function willLintPaths(array $paths) {
|
||||
if (!xhpast_is_available()) {
|
||||
throw new Exception(xhpast_get_build_instructions());
|
||||
}
|
||||
|
||||
// NOTE: For now, we completely ignore paths and just lint every library in
|
||||
// its entirety. This is simpler and relatively fast because we don't do any
|
||||
// detailed checks and all the data we need for this comes out of module
|
||||
// caches.
|
||||
|
||||
$bootloader = PhutilBootloader::getInstance();
|
||||
$libs = $bootloader->getAllLibraries();
|
||||
|
||||
// Load the up-to-date map for each library, without loading the library
|
||||
// itself. This means lint results will accurately reflect the state of
|
||||
// the working copy.
|
||||
|
||||
$arc_root = dirname(phutil_get_library_root('arcanist'));
|
||||
$bin = "{$arc_root}/scripts/phutil_rebuild_map.php";
|
||||
|
||||
$symbols = array();
|
||||
foreach ($libs as $lib) {
|
||||
// Do these one at a time since they individually fanout to saturate
|
||||
// available system resources.
|
||||
$future = new ExecFuture(
|
||||
'%s --show --quiet --ugly -- %s',
|
||||
$bin,
|
||||
phutil_get_library_root($lib));
|
||||
$symbols[$lib] = $future->resolveJSON();
|
||||
}
|
||||
|
||||
$all_symbols = array();
|
||||
foreach ($symbols as $library => $map) {
|
||||
// Check for files which declare more than one class/interface in the same
|
||||
// file, or mix function definitions with class/interface definitions. We
|
||||
// must isolate autoloadable symbols to one per file so the autoloader
|
||||
// can't end up in an unresolvable cycle.
|
||||
foreach ($map as $file => $spec) {
|
||||
$have = idx($spec, 'have', array());
|
||||
|
||||
$have_classes =
|
||||
idx($have, 'class', array()) +
|
||||
idx($have, 'interface', array());
|
||||
$have_functions = idx($have, 'function');
|
||||
|
||||
if ($have_functions && $have_classes) {
|
||||
$function_list = implode(', ', array_keys($have_functions));
|
||||
$class_list = implode(', ', array_keys($have_classes));
|
||||
$this->raiseLintInLibrary(
|
||||
$library,
|
||||
$file,
|
||||
end($have_functions),
|
||||
self::LINT_ONE_CLASS_PER_FILE,
|
||||
"File '{$file}' mixes function ({$function_list}) and ".
|
||||
"class/interface ({$class_list}) definitions in the same file. ".
|
||||
"A file which declares a class or an interface MUST ".
|
||||
"declare nothing else.");
|
||||
} else if (count($have_classes) > 1) {
|
||||
$class_list = implode(', ', array_keys($have_classes));
|
||||
$this->raiseLintInLibrary(
|
||||
$library,
|
||||
$file,
|
||||
end($have_classes),
|
||||
self::LINT_ONE_CLASS_PER_FILE,
|
||||
"File '{$file}' declares more than one class or interface ".
|
||||
"({$class_list}). A file which declares a class or interface MUST ".
|
||||
"declare nothing else.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate symbols: two files providing the same class or
|
||||
// function.
|
||||
foreach ($map as $file => $spec) {
|
||||
$have = idx($spec, 'have', array());
|
||||
foreach (array('class', 'function', 'interface') as $type) {
|
||||
$libtype = ($type == 'interface') ? 'class' : $type;
|
||||
foreach (idx($have, $type, array()) as $symbol => $offset) {
|
||||
if (empty($all_symbols[$libtype][$symbol])) {
|
||||
$all_symbols[$libtype][$symbol] = array(
|
||||
'library' => $library,
|
||||
'file' => $file,
|
||||
'offset' => $offset,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$osrc = $all_symbols[$libtype][$symbol]['file'];
|
||||
$olib = $all_symbols[$libtype][$symbol]['library'];
|
||||
|
||||
$this->raiseLintInLibrary(
|
||||
$library,
|
||||
$file,
|
||||
$offset,
|
||||
self::LINT_DUPLICATE_SYMBOL,
|
||||
"Definition of {$type} '{$symbol}' in '{$file}' in library ".
|
||||
"'{$library}' duplicates prior definition in '{$osrc}' in ".
|
||||
"library '{$olib}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($symbols as $library => $map) {
|
||||
// Check for unknown symbols: uses of classes, functions or interfaces
|
||||
// which are not defined anywhere. We reference the list of all symbols
|
||||
// we built up earlier.
|
||||
foreach ($map as $file => $spec) {
|
||||
$need = idx($spec, 'need', array());
|
||||
foreach (array('class', 'function', 'interface') as $type) {
|
||||
$libtype = ($type == 'interface') ? 'class' : $type;
|
||||
foreach (idx($need, $type, array()) as $symbol => $offset) {
|
||||
if (!empty($all_symbols[$libtype][$symbol])) {
|
||||
// Symbol is defined somewhere.
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintInLibrary(
|
||||
$library,
|
||||
$file,
|
||||
$offset,
|
||||
self::LINT_UNKNOWN_SYMBOL,
|
||||
"Use of unknown {$type} '{$symbol}'. This symbol is not defined ".
|
||||
"in any loaded phutil library.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function raiseLintInLibrary($library, $path, $offset, $code, $desc) {
|
||||
$root = phutil_get_library_root($library);
|
||||
|
||||
$this->activePath = $root.'/'.$path;
|
||||
$this->raiseLintAtOffset($offset, $code, $desc);
|
||||
}
|
||||
|
||||
public function lintPath($path) {
|
||||
return;
|
||||
}
|
||||
}
|
17
src/lint/linter/phutillibrary/__init__.php
Normal file
17
src/lint/linter/phutillibrary/__init__.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('arcanist', 'lint/linter/base');
|
||||
|
||||
phutil_require_module('phutil', 'future/exec');
|
||||
phutil_require_module('phutil', 'moduleutils');
|
||||
phutil_require_module('phutil', 'parser/xhpast/bin');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('ArcanistPhutilLibraryLinter.php');
|
Loading…
Reference in a new issue