1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 06:42: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:
epriestley 2012-05-30 07:25:09 -07:00
parent 61b0cfac63
commit 009e6c4dbf
4 changed files with 273 additions and 4 deletions

View file

@ -47,6 +47,15 @@ $args->parse(
'help' => 'Controls the number of symbol mapper subprocesses run '. 'help' => 'Controls the number of symbol mapper subprocesses run '.
'at once. Defaults to 8.', '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( array(
'name' => 'root', 'name' => 'root',
'wildcard' => true, 'wildcard' => true,
@ -68,6 +77,11 @@ if ($args->getArg('drop-cache')) {
$builder->dropSymbolCache(); $builder->dropSymbolCache();
} }
if ($args->getArg('show')) {
$builder->setShowMap(true);
$builder->setUgly($args->getArg('ugly'));
}
$builder->buildMap(); $builder->buildMap();
exit(0); exit(0);
@ -89,6 +103,8 @@ final class PhutilLibraryMapBuilder {
private $root; private $root;
private $quiet; private $quiet;
private $subprocessLimit = 8; private $subprocessLimit = 8;
private $ugly;
private $showMap;
const LIBRARY_MAP_VERSION_KEY = '__library_version__'; const LIBRARY_MAP_VERSION_KEY = '__library_version__';
const LIBRARY_MAP_VERSION = 2; 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. * Build or rebuild the library map.
* *
@ -196,11 +242,25 @@ final class PhutilLibraryMapBuilder {
// out old cache entries. // out old cache entries.
$this->writeSymbolCache($symbol_map, $source_map); $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"); // Our map is up to date, so either show it on stdout or write it to disk.
$this->writeLibraryMap($library_map);
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"); $this->log("Done.\n");

View file

@ -82,6 +82,7 @@ phutil_register_library_map(array(
'ArcanistPasteWorkflow' => 'workflow/paste', 'ArcanistPasteWorkflow' => 'workflow/paste',
'ArcanistPatchWorkflow' => 'workflow/patch', 'ArcanistPatchWorkflow' => 'workflow/patch',
'ArcanistPhpcsLinter' => 'lint/linter/phpcs', 'ArcanistPhpcsLinter' => 'lint/linter/phpcs',
'ArcanistPhutilLibraryLinter' => 'lint/linter/phutillibrary',
'ArcanistPhutilModuleLinter' => 'lint/linter/phutilmodule', 'ArcanistPhutilModuleLinter' => 'lint/linter/phutilmodule',
'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase', 'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase',
'ArcanistPhutilTestSkippedException' => 'unit/engine/phutil/testcase/exception', 'ArcanistPhutilTestSkippedException' => 'unit/engine/phutil/testcase/exception',
@ -174,6 +175,7 @@ phutil_register_library_map(array(
'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPhpcsLinter' => 'ArcanistLinter', 'ArcanistPhpcsLinter' => 'ArcanistLinter',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilModuleLinter' => 'ArcanistLinter', 'ArcanistPhutilModuleLinter' => 'ArcanistLinter',
'ArcanistPyFlakesLinter' => 'ArcanistLinter', 'ArcanistPyFlakesLinter' => 'ArcanistLinter',
'ArcanistPyLintLinter' => 'ArcanistLinter', 'ArcanistPyLintLinter' => 'ArcanistLinter',

View 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;
}
}

View 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');