1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-01-22 20:51:09 +01:00

Improve Arcanist symbol name linter

Summary:
  - Move name helper functions to ArcanistXHPASTLintNamingHook to make it easier
to write custom linters.
  - Add test coverage for name functions.
  - Add 'variable' and 'global' naming convention tests.
  - Expand test cases.
  - Improve lint message error when an unexpected message is raised during a
test.
  - Remove a defunct XHP lint message.

Test Plan:
  - Ran unit tests.
  - Ran "arc lint --lintall" on arcanist/.

Reviewers: btrahan, nh, jungejason

Reviewed By: btrahan

CC: johnduhart, aran, epriestley, arudolph

Differential Revision: https://secure.phabricator.com/D1506
This commit is contained in:
epriestley 2012-01-28 11:17:45 -08:00
parent 975b541d26
commit 0781554a22
9 changed files with 370 additions and 71 deletions

View file

@ -99,6 +99,7 @@ phutil_register_library_map(array(
'ArcanistWhichWorkflow' => 'workflow/which', 'ArcanistWhichWorkflow' => 'workflow/which',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity', 'ArcanistWorkingCopyIdentity' => 'workingcopyidentity',
'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/naminghook', 'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/naminghook',
'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/naminghook/__tests__',
'ArcanistXHPASTLinter' => 'lint/linter/xhpast', 'ArcanistXHPASTLinter' => 'lint/linter/xhpast',
'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__', 'ArcanistXHPASTLinterTestCase' => 'lint/linter/xhpast/__tests__',
'BranchInfo' => 'branch', 'BranchInfo' => 'branch',
@ -170,6 +171,7 @@ phutil_register_library_map(array(
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUserAbortException' => 'ArcanistUsageException', 'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistWhichWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistWhichWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistXHPASTLintNamingHookTestCase' => 'ArcanistPhutilTestCase',
'ArcanistXHPASTLinter' => 'ArcanistLinter', 'ArcanistXHPASTLinter' => 'ArcanistLinter',
'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
'ComprehensiveLintEngine' => 'ArcanistLintEngine', 'ComprehensiveLintEngine' => 'ArcanistLintEngine',

View file

@ -118,13 +118,16 @@ abstract class ArcanistLinterTestCase extends ArcanistPhutilTestCase {
private function compareLint($file, $expect, $result) { private function compareLint($file, $expect, $result) {
$seen = array(); $seen = array();
$raised = array(); $raised = array();
$message_map = array();
foreach ($result->getMessages() as $message) { foreach ($result->getMessages() as $message) {
$sev = $message->getSeverity(); $sev = $message->getSeverity();
$line = $message->getLine(); $line = $message->getLine();
$char = $message->getChar(); $char = $message->getChar();
$code = $message->getCode(); $code = $message->getCode();
$name = $message->getName(); $name = $message->getName();
$seen[] = $sev.":".$line.":".$char; $message_key = $sev.":".$line.":".$char;
$message_map[$message_key] = $message;
$seen[] = $message_key;
$raised[] = " {$sev} at line {$line}, char {$char}: {$code} {$name}"; $raised[] = " {$sev} at line {$line}, char {$char}: {$code} {$name}";
} }
$expect = trim($expect); $expect = trim($expect);
@ -154,11 +157,15 @@ abstract class ArcanistLinterTestCase extends ArcanistPhutilTestCase {
} }
foreach (array_diff_key($seen, $expect) as $surprising => $ignored) { foreach (array_diff_key($seen, $expect) as $surprising => $ignored) {
$message = $message_map[$surprising];
$message_info = $message->getDescription();
list($sev, $line, $char) = explode(':', $surprising); list($sev, $line, $char) = explode(':', $surprising);
$this->assertFailure( $this->assertFailure(
"In '{$file}', ". "In '{$file}', ".
"lint raised {$sev} on line {$line} at char {$char}, ". "lint raised {$sev} on line {$line} at char {$char}, ".
"but nothing was expected. {$raised}"); "but nothing was expected:\n\n{$message_info}\n\n{$raised}");
} }
} }

View file

@ -455,16 +455,7 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
$declarations = array( $declarations = array(
'$this' => 0, '$this' => 0,
'$GLOBALS' => 0, ) + array_fill_keys($this->getSuperGlobalNames(), 0);
'$_SERVER' => 0,
'$_GET' => 0,
'$_POST' => 0,
'$_FILES' => 0,
'$_COOKIE' => 0,
'$_SESSION' => 0,
'$_REQUEST' => 0,
'$_ENV' => 0,
);
$declaration_tokens = array(); $declaration_tokens = array();
$exclude_tokens = array(); $exclude_tokens = array();
$vars = array(); $vars = array();
@ -711,29 +702,17 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
foreach ($classes as $class) { foreach ($classes as $class) {
$name_token = $class->getChildByIndex(1); $name_token = $class->getChildByIndex(1);
$name_string = $name_token->getConcreteString(); $name_string = $name_token->getConcreteString();
$is_xhp = ($name_string[0] == ':');
if ($is_xhp) {
$names[] = array(
'xhp-class',
$name_string,
$name_token,
$this->isLowerCaseWithXHP($name_string)
? null
: 'Follow naming conventions: XHP elements should be named using '.
'lower case.',
);
} else {
$names[] = array( $names[] = array(
'class', 'class',
$name_string, $name_string,
$name_token, $name_token,
$this->isUpperCamelCase($name_string) ArcanistXHPASTLintNamingHook::isUpperCamelCase($name_string)
? null ? null
: 'Follow naming conventions: classes should be named using '. : 'Follow naming conventions: classes should be named using '.
'UpperCamelCase.', 'UpperCamelCase.',
); );
} }
}
$ifaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION'); $ifaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
foreach ($ifaces as $iface) { foreach ($ifaces as $iface) {
@ -743,7 +722,7 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
'interface', 'interface',
$name_string, $name_string,
$name_token, $name_token,
$this->isUpperCamelCase($name_string) ArcanistXHPASTLintNamingHook::isUpperCamelCase($name_string)
? null ? null
: 'Follow naming conventions: interfaces should be named using '. : 'Follow naming conventions: interfaces should be named using '.
'UpperCamelCase.', 'UpperCamelCase.',
@ -763,7 +742,8 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
'function', 'function',
$name_string, $name_string,
$name_token, $name_token,
$this->isLowercaseWithUnderscores($name_string) ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(
ArcanistXHPASTLintNamingHook::stripPHPFunction($name_string))
? null ? null
: 'Follow naming conventions: functions should be named using '. : 'Follow naming conventions: functions should be named using '.
'lowercase_with_underscores.', 'lowercase_with_underscores.',
@ -779,24 +759,32 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
'method', 'method',
$name_string, $name_string,
$name_token, $name_token,
$this->isLowerCamelCase($name_string) ArcanistXHPASTLintNamingHook::isLowerCamelCase(
ArcanistXHPASTLintNamingHook::stripPHPFunction($name_string))
? null ? null
: 'Follow naming conventions: methods should be named using '. : 'Follow naming conventions: methods should be named using '.
'lowerCamelCase.', 'lowerCamelCase.',
); );
} }
$param_tokens = array();
$params = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER_LIST'); $params = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER_LIST');
foreach ($params as $param_list) { foreach ($params as $param_list) {
foreach ($param_list->getChildren() as $param) { foreach ($param_list->getChildren() as $param) {
$name_token = $param->getChildByIndex(1); $name_token = $param->getChildByIndex(1);
if ($name_token->getTypeName() == 'n_VARIABLE_REFERENCE') {
$name_token = $name_token->getChildOfType(0, 'n_VARIABLE');
}
$param_tokens[$name_token->getID()] = true;
$name_string = $name_token->getConcreteString(); $name_string = $name_token->getConcreteString();
$names[] = array( $names[] = array(
'parameter', 'parameter',
$name_string, $name_string,
$name_token, $name_token,
$this->isLowercaseWithUnderscores($name_string) ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(
ArcanistXHPASTLintNamingHook::stripPHPVariable($name_string))
? null ? null
: 'Follow naming conventions: parameters should be named using '. : 'Follow naming conventions: parameters should be named using '.
'lowercase_with_underscores.', 'lowercase_with_underscores.',
@ -815,7 +803,7 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
'constant', 'constant',
$name_string, $name_string,
$name_token, $name_token,
$this->isUppercaseWithUnderscores($name_string) ArcanistXHPASTLintNamingHook::isUppercaseWithUnderscores($name_string)
? null ? null
: 'Follow naming conventions: class constants should be named '. : 'Follow naming conventions: class constants should be named '.
'using UPPERCASE_WITH_UNDERSCORES.', 'using UPPERCASE_WITH_UNDERSCORES.',
@ -823,19 +811,25 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
} }
} }
$member_tokens = array();
$props = $root->selectDescendantsOfType('n_CLASS_MEMBER_DECLARATION_LIST'); $props = $root->selectDescendantsOfType('n_CLASS_MEMBER_DECLARATION_LIST');
foreach ($props as $prop_list) { foreach ($props as $prop_list) {
foreach ($prop_list->getChildren() as $prop) { foreach ($prop_list->getChildren() as $token_id => $prop) {
if ($prop->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST') { if ($prop->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST') {
continue; continue;
} }
$name_token = $prop->getChildByIndex(0); $name_token = $prop->getChildByIndex(0);
$member_tokens[$name_token->getID()] = true;
$name_string = $name_token->getConcreteString(); $name_string = $name_token->getConcreteString();
$names[] = array( $names[] = array(
'member', 'member',
$name_string, $name_string,
$name_token, $name_token,
$this->isLowerCamelCase($name_string) ArcanistXHPASTLintNamingHook::isLowerCamelCase(
ArcanistXHPASTLintNamingHook::stripPHPVariable($name_string))
? null ? null
: 'Follow naming conventions: class properties should be named '. : 'Follow naming conventions: class properties should be named '.
'using lowerCamelCase.', 'using lowerCamelCase.',
@ -843,6 +837,84 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
} }
} }
$superglobal_map = array_fill_keys(
$this->getSuperGlobalNames(),
true);
$fdefs = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
$mdefs = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
$defs = $fdefs->add($mdefs);
foreach ($defs as $def) {
$globals = $def->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
$globals = $globals->selectDescendantsOfType('n_VARIABLE');
$globals_map = array();
foreach ($globals as $global) {
$global_string = $global->getConcreteString();
$globals_map[$global_string] = true;
$names[] = array(
'global',
$global_string,
$global,
// No advice for globals, but hooks have an option to provide some.
null);
}
// Exclude access of static properties, since lint will be raised at
// their declaration if they're invalid and they may not conform to
// variable rules. This is slightly overbroad (includes the entire
// rhs of a "Class::..." token) to cover cases like "Class:$x[0]". These
// varaibles are simply made exempt from naming conventions.
$exclude_tokens = array();
$statics = $def->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
foreach ($statics as $static) {
$rhs = $static->getChildByIndex(1);
$rhs_vars = $def->selectDescendantsOfType('n_VARIABLE');
foreach ($rhs_vars as $var) {
$exclude_tokens[$var->getID()] = true;
}
}
$vars = $def->selectDescendantsOfType('n_VARIABLE');
foreach ($vars as $token_id => $var) {
if (isset($member_tokens[$token_id])) {
continue;
}
if (isset($param_tokens[$token_id])) {
continue;
}
if (isset($exclude_tokens[$token_id])) {
continue;
}
$var_string = $var->getConcreteString();
// Awkward artifact of "$o->{$x}".
$var_string = trim($var_string, '{}');
if (isset($superglobal_map[$var_string])) {
continue;
}
if (isset($globals_map[$var_string])) {
continue;
}
$names[] = array(
'variable',
$var_string,
$var,
ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(
ArcanistXHPASTLintNamingHook::stripPHPVariable($var_string))
? null
: 'Follow naming conventions: variables should be named using '.
'lowercase_with_underscores.',
);
}
}
$engine = $this->getEngine(); $engine = $this->getEngine();
$working_copy = $engine->getWorkingCopy(); $working_copy = $engine->getWorkingCopy();
@ -872,28 +944,6 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
} }
} }
protected function isUpperCamelCase($str) {
return preg_match('/^[A-Z][A-Za-z0-9]*$/', $str);
}
protected function isLowerCamelCase($str) {
// Allow initial "__" for magic methods like __construct; we could also
// enumerate these explicitly.
return preg_match('/^\$?(?:__)?[a-z][A-Za-z0-9]*$/', $str);
}
protected function isUppercaseWithUnderscores($str) {
return preg_match('/^[A-Z0-9_]+$/', $str);
}
protected function isLowercaseWithUnderscores($str) {
return preg_match('/^[&]?\$?[a-z0-9_]+$/', $str);
}
protected function isLowercaseWithXHP($str) {
return preg_match('/^:[a-z0-9_:-]+$/', $str);
}
protected function lintSurpriseConstructors($root) { protected function lintSurpriseConstructors($root) {
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) { foreach ($classes as $class) {
@ -1352,4 +1402,18 @@ class ArcanistXHPASTLinter extends ArcanistLinter {
$replace); $replace);
} }
public function getSuperGlobalNames() {
return array(
'$GLOBALS',
'$_SERVER',
'$_GET',
'$_POST',
'$_FILES',
'$_COOKIE',
'$_SESSION',
'$_REQUEST',
'$_ENV',
);
}
} }

View file

@ -7,10 +7,11 @@
phutil_require_module('arcanist', 'lint/linter/base'); phutil_require_module('arcanist', 'lint/linter/base');
phutil_require_module('arcanist', 'lint/linter/xhpast/naminghook');
phutil_require_module('arcanist', 'lint/severity'); phutil_require_module('arcanist', 'lint/severity');
phutil_require_module('phutil', 'parser/xhpast/api/tree'); phutil_require_module('phutil', 'parser/xhpast/api/tree');
phutil_require_module('phutil', 'parser/xhpast/bin'); phutil_require_module('phutil', 'parser/xhpast/bin');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -23,8 +23,8 @@ $d = array(
$e = array( $e = array(
self::B => 'bee', self::B => 'bee',
self::B => 'bea', self::B => 'bea',
self::B() => 'ehh', self::b() => 'ehh',
self::$B => 'weh', self::$b => 'weh',
B => 'b', B => 'b',
C::B => 'cb', C::B => 'cb',
); );

View file

@ -25,6 +25,30 @@ function () use ($this_is_a_closure) { };
function f(&$YY) { function f(&$YY) {
} }
function g() {
$lowerCamelCase = 0;
$UpperCamelCase = 0;
$UPPERCASE_WITH_UNDERSCORES = 0;
$lowercase_with_underscores = 0;
$mIxEd_CaSe = 0;
}
function h() {
global $mIxEd_CaSe;
$mIxEd_CaSe = 2;
$GLOBALS[0] = 2;
$_ENV[0] = 1;
}
function i() {
self::$X_x;
Other::$Y_y;
parent::$Z_z;
self::$X_x[0];
Other::$Y_y[0];
parent::$Z_z[0];
}
~~~~~~~~~~ ~~~~~~~~~~
warning:2:7 warning:2:7
warning:3:9 warning:3:9
@ -37,4 +61,8 @@ warning:5:25
warning:8:11 warning:8:11
warning:12:10 warning:12:10
warning:12:13 warning:12:13
warning:26:12 warning:26:13
warning:30:3
warning:31:3
warning:32:3
warning:34:3

View file

@ -1,7 +1,7 @@
<?php <?php
/* /*
* Copyright 2011 Facebook, Inc. * Copyright 2012 Facebook, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,18 +17,37 @@
*/ */
/** /**
* You can extend this class and set "lint.xhpast.naminghook" in your * You can extend this class and set ##"lint.xhpast.naminghook"## in your
* .arcconfig to have an opportunity to override lint results for symbol names. * ##.arcconfig## to have an opportunity to override lint results for symbol
* names.
* *
* @task override Overriding Symbol Name Lint * @task override Overriding Symbol Name Lint Messages
* @task util Name Utilities
* @task internal Internals
* @group lint * @group lint
* @stable
*/ */
abstract class ArcanistXHPASTLintNamingHook { abstract class ArcanistXHPASTLintNamingHook {
/* -( Internals )---------------------------------------------------------- */
/**
* The constructor is final because @{class:ArcanistXHPASTLinter} is
* responsible for hook instantiation.
*
* @return this
* @task internals
*/
final public function __construct() { final public function __construct() {
// <empty> // <empty>
} }
/* -( Overriding Symbol Name Lint Messages )------------------------------- */
/** /**
* Callback invoked for each symbol, which can override the default * Callback invoked for each symbol, which can override the default
* determination of name validity or accept it by returning $default. The * determination of name validity or accept it by returning $default. The
@ -57,4 +76,85 @@ abstract class ArcanistXHPASTLintNamingHook {
*/ */
abstract public function lintSymbolName($type, $name, $default); abstract public function lintSymbolName($type, $name, $default);
/* -( Name Utilities )----------------------------------------------------- */
/**
* Returns true if a symbol name is UpperCamelCase.
*
* @param string Symbol name.
* @return bool True if the symbol is UpperCamelCase.
* @task util
*/
public static function isUpperCamelCase($symbol) {
return preg_match('/^[A-Z][A-Za-z0-9]*$/', $symbol);
}
/**
* Returns true if a symbol name is lowerCamelCase.
*
* @param string Symbol name.
* @return bool True if the symbol is lowerCamelCase.
* @task util
*/
public static function isLowerCamelCase($symbol) {
return preg_match('/^[a-z][A-Za-z0-9]*$/', $symbol);
}
/**
* Returns true if a symbol name is UPPERCASE_WITH_UNDERSCORES.
*
* @param string Symbol name.
* @return bool True if the symbol is UPPERCASE_WITH_UNDERSCORES.
* @task util
*/
public static function isUppercaseWithUnderscores($symbol) {
return preg_match('/^[A-Z0-9_]+$/', $symbol);
}
/**
* Returns true if a symbol name is lowercase_with_underscores.
*
* @param string Symbol name.
* @return bool True if the symbol is lowercase_with_underscores.
* @task util
*/
public static function isLowercaseWithUnderscores($symbol) {
return preg_match('/^[a-z0-9_]+$/', $symbol);
}
/**
* Strip non-name components from PHP function symbols. Notably, this discards
* the "__" magic-method signifier, to make a symbol appropriate for testing
* with methods like @{method:isLowerCamelCase}.
*
* @param string Symbol name.
* @return string Stripped symbol.
* @task util
*/
public static function stripPHPFunction($symbol) {
// Allow initial "__" for magic methods like __construct; we could also
// enumerate these explicitly.
return preg_replace('/^__/', '', $symbol);
}
/**
* Strip non-name components from PHP variable symbols. Notably, this discards
* the "$", to make a symbol appropriate for testing with methods like
* @{method:isLowercaseWithUnderscores}.
*
* @param string Symbol name.
* @return bool True if the symbol is UpperCamelCase.
* @task util
*/
public static function stripPHPVariable($symbol) {
return preg_replace('/^\$/', '', $symbol);
}
} }

View file

@ -0,0 +1,84 @@
<?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.
*/
/**
* Test cases for @{class:ArcanistXHPASTLintNamingHook}.
*
* @group testcase
*/
final class ArcanistXHPASTLintNamingHookTestCase
extends ArcanistPhutilTestCase {
public function testCaseUtilities() {
$tests = array(
'UpperCamelCase' => array(1, 0, 0, 0),
'UpperCamelCaseROFL' => array(1, 0, 0, 0),
'lowerCamelCase' => array(0, 1, 0, 0),
'lowerCamelCaseROFL' => array(0, 1, 0, 0),
'UPPERCASE_WITH_UNDERSCORES' => array(0, 0, 1, 0),
'_UPPERCASE_WITH_UNDERSCORES_' => array(0, 0, 1, 0),
'__UPPERCASE__WITH__UNDERSCORES__' => array(0, 0, 1, 0),
'lowercase_with_underscores' => array(0, 0, 0, 1),
'_lowercase_with_underscores_' => array(0, 0, 0, 1),
'__lowercase__with__underscores__' => array(0, 0, 0, 1),
'mixedCASE_NoNsEnSe' => array(0, 0, 0, 0),
);
foreach ($tests as $test => $expect) {
$this->assertEqual(
$expect[0],
ArcanistXHPASTLintNamingHook::isUpperCamelCase($test),
"UpperCamelCase: '{$test}'");
$this->assertEqual(
$expect[1],
ArcanistXHPASTLintNamingHook::isLowerCamelCase($test),
"lowerCamelCase: '{$test}'");
$this->assertEqual(
$expect[2],
ArcanistXHPASTLintNamingHook::isUppercaseWithUnderscores($test),
"UPPERCASE_WITH_UNDERSCORES: '{$test}'");
$this->assertEqual(
$expect[3],
ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores($test),
"lowercase_with_underscores: '{$test}'");
}
}
public function testStripUtilities() {
// Variable stripping.
$this->assertEqual(
'stuff',
ArcanistXHPASTLintNamingHook::stripPHPVariable('stuff'));
$this->assertEqual(
'stuff',
ArcanistXHPASTLintNamingHook::stripPHPVariable('$stuff'));
// Function/method stripping.
$this->assertEqual(
'construct',
ArcanistXHPASTLintNamingHook::stripPHPFunction('construct'));
$this->assertEqual(
'construct',
ArcanistXHPASTLintNamingHook::stripPHPFunction('__construct'));
}
}

View file

@ -0,0 +1,13 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('arcanist', 'lint/linter/xhpast/naminghook');
phutil_require_module('arcanist', 'unit/engine/phutil/testcase');
phutil_require_source('ArcanistXHPASTLintNamingHookTestCase.php');