Определите, является ли строка camelCase
Я пытаюсь заставить PHP_CodeSniffer проверять наличие camelCase в именах классов, однако мне кажется, что проверка camelCase невозможна (без словаря, в том числе словечек).
Я грабил интернет, но пока единственные варианты, которые я видел, были бы, если бы у строки был какой-то общий разделитель, из которого можно взорвать - то есть подчеркивание, пробел между словами и т. Д.
И даже это бесполезно, поскольку проверка может быть точной, только если имя точно / всегда содержит разделитель между каждым словом.
И точка "проверки" будет заключаться в том, чтобы определить, правильно ли отформатировано имя, и это может включать неправильное разделение.
Кроме того, ресурсы в PHP_CodeSniffer либо редки, либо настолько просты и понятны, что их понимает только автор / разработчик.
Текущие Стандартные Проверки Sniff
Я нашел этот код в некоторых из текущих Sniffs (то есть стандартов Squiz и PEAR):
if (PHP_CodeSniffer::isCamelCaps($functionName, false, true, false) === false)
Тем не менее, я посмотрел на основной код PHP_CodeSniffer, и эта функция только делает следующее:
// Check the first character first.
// Check that the name only contains legal characters.
// Check that there are not two capital letters next to each other.
// The character is a number, so it cant be a capital.
Эти базовые проверки лучше, чем ничего, хотя, возможно, бесполезны для предполагаемой цели, так как на самом деле они вообще не проверяют наличие camelCase.
Вопрос
Как может Sniff (или, например, PHP-скрипт) знать, какие "слова" нужно проверять в данной строке, чтобы определить, является ли строка 100% camelCase?
РЕДАКТИРОВАТЬ
Примеры
Правильный camelCase: class calculateAdminLoginCount
// Not camelCase
class calculateadminlogincount
// Partially camelCase
class calculateadminLogincount
Как можно isCamelCaps()
функция (или любой PHP-скрипт в этом отношении) ловит два приведенных выше примера?
Как функция или PHP-скрипт могут идентифицировать "отдельные слова" из строки, если у них нет понятия "слова", не передавая им эту информацию (то есть из словаря)?
Даже если сценарий, где взорваться, что бы он взорвался на основе?
принимать class calculateadminLogincount
Как любой скрипт PHP может идентифицировать это calculate
admin
Login
count
разные слова в этой строке, чтобы потом можно было проверить, если: 1-я буква 1-е слово строчная, тогда все последующие слова 1-я буква прописная?
isCamelCaps()
функция
public static function isCamelCaps(
$string,
$classFormat=false,
$public=true,
$strict=true
) {
// Check the first character first.
if ($classFormat === false) {
$legalFirstChar = '';
if ($public === false) {
$legalFirstChar = '[_]';
}
if ($strict === false) {
// Can either start with a lowercase letter,
// or multiple uppercase
// in a row, representing an acronym.
$legalFirstChar .= '([A-Z]{2,}|[a-z])';
} else {
$legalFirstChar .= '[a-z]';
}
} else {
$legalFirstChar = '[A-Z]';
}
if (preg_match("/^$legalFirstChar/", $string) === 0) {
return false;
}
// Check that the name only contains legal characters.
$legalChars = 'a-zA-Z0-9';
if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
return false;
}
if ($strict === true) {
// Check that there are not two capital letters
// next to each other.
$length = strlen($string);
$lastCharWasCaps = $classFormat;
for ($i = 1; $i < $length; $i++) {
$ascii = ord($string{$i});
if ($ascii >= 48 && $ascii <= 57) {
// The character is a number, so it cant be a capital.
$isCaps = false;
} else {
if (strtoupper($string{$i}) === $string{$i}) {
$isCaps = true;
} else {
$isCaps = false;
}
}
if ($isCaps === true && $lastCharWasCaps === true) {
return false;
}
$lastCharWasCaps = $isCaps;
}
}//end if
return true;
}//end isCamelCaps()
РЕДАКТИРОВАТЬ 2
Немного информации для тех, кто интересуется, стоит ли это того или нет, или я просто "бездельничаю" и "получаю удовольствие":
Обязательно, чтобы имена классов были правильно названы повсюду, поскольку структура файла / папки, а также имена и имена классов должны совпадать, чтобы автозагрузчик работал без сбоев.
Хотя у меня есть проверки в самом коде ядра для проверки и обработки таких проблем, если скрипт, класс и т. Д. Не могут быть загружены (конечно), нет ничего плохого в том, что дополнительный сценарий (PHP_CodeSniffer) запускает все файлы и сообщает мне, где потенциальный вопрос может лгать.
Даже если это всего лишь для второй проверки, тем более что она обеспечивает аккуратность, правильную структуру и непрерывность кода.
3 ответа
Я сделал несколько сценариев, чтобы попытаться "свободно" определить, является ли имя класса camelCase.
Некоторые сценарии, которые я написал для своего сценария, не помогут другим, например, они слишком специфичны для моих собственных соглашений об именах (я их здесь не включал).
Так что моя настоящая коллекция сценариев делает все это стоящим, но, надеюсь, приведенные ниже более общие помогут кому-то еще.
Например, я добавляю имена классов в нижнем регистре, поэтому проверяю, является ли слово после этого префикса прописным.
Для тех (большинство людей), которые не ставят имена классов перед определенным словом, достаточно просто проверить, что первый символ строки в нижнем регистре.
Критика очень приветствуется.
Разрешить только альфа-регистр
Это гарантирует, что имя класса содержит только прописные или строчные буквенные буквы (Az), которые необходимы для проверок camelCase (если вы удалите этот сценарий, вам придется изменить другие сценарии, чтобы приспособиться к потенциалу не-альфа символы).
/** Check string is only alpha (A-z) */
if (ctype_alpha($name) === false) {
$error = '%s name must only contain alpha chars (A-z)';
$phpcsFile->addError($error, $stackPtr, 'AlphaChars', $errorData);
return;
}
Нет двух заглавных букв вместе
Некоторые стандарты допускают аббревиатуры и т. Д., Однако мои стандарты не допускают этого, так как это не строгий camelCase и нарушает поток чтения.
например userSitePHPLogin
является недействительным, и userSitePhpLogin
является действительным.
(Есть более элегантный способ сделать это, но он работает нормально, и, учитывая, что он для PHP_CodeSniffer, мне не нужна микрооптимизация)
/** Check for uppercase chars together */
$nameUppercaseExplode = preg_split('/(?=[A-Z])/', $name);
$totalIllegalUpperChars = 0;
foreach ($nameUppercaseExplode as $namePiece) {
if (strlen($namePiece) == 1) {
$totalIllegalUpperChars++;
}
}
if ($totalIllegalUpperChars >0) {
$warning = 'Class name seems invalid;
Total '.$totalIllegalUpperChars.' uppercase chars not part of camelCase';
$phpcsFile->addWarning($warning, $stackPtr, 'UppercaseTogether', $errorData);
}
например, имя класса DUserPHPUserclassLogin
возвращает:
Имя класса кажется неверным; Всего 4 заглавных буквы, не являющихся частью camelCase
Это не идеально, так как это 1 на этом чеке.
Но он вернет предупреждение только в том случае, если есть хотя бы 1 вхождение заглавных букв вместе.
например, имя класса classDUserPhpUserLogin
возвращает:
Имя класса кажется неверным; Всего 1 заглавные буквы не являются частью camelCase
Так что это, по крайней мере, побуждает разработчика проверить имя и исправить его соответствующим образом.
Проверьте, если общее количество символов в верхнем регистре меньше общего количества слов
Спасибо sjagr за идею.
"Всего слов" - это, конечно, "угаданная" цифра, основанная на среднем 5 знаков для каждого слова - потому что кажется, что официальное среднее значение составляет около 4,7 знаков для каждого слова.
/** Loose check if total (guessed) words not match total uppercase chars */
$totalWordsGuess = ceil(strlen($name) / 5);
$totalUpperChars = strlen(preg_replace('![^A-Z]+!', '', $name));
// Pointless if only 1 word (camelCase not exist)
if ($totalWordsGuess >1) {
// Remove the first word which should be lowercase
// (first word should be checked in separate check above this one)
$totalWordsGuess--;
if ($totalUpperChars < $totalWordsGuess) {
$warning = 'Expected '.$totalWordsGuess.' camelCase words in class name;
Found '.$totalUpperChars;
$phpcsFile->addWarning($warning, $stackPtr, 'BadCamelCase', $errorData);
}
}
Я проверил это и работает довольно хорошо (это только предупреждение для потенциальных проблем).
Например, используя имя класса UserLoginToomanywordsWithoutcamelCase
PHP_CodeSniffer возвращает:
Ожидаемые 7 слов camelCase в названии класса; Найдено 5
Если возвращается слишком много ложных срабатываний (разные разработчики используют разные слова и т. Д.), То настройте текущее значение "5" вверх или вниз на ступеньку выше.
Изменить: Обновлен этот скрипт выше:
- Добавлено условие, чтобы скрипт выполнялся только если больше 1 слова, так как 1 слово не может быть camelCase.
- Добавлен код для вычитания 1 из общего количества угаданных слов (
var --
), чтобы учесть, что первое слово в нижнем регистре, и поэтому для него не будет никакого количества заглавных букв.
Вы должны иметь отдельную проверку выше этой, чтобы проверить первое слово, которое returns
если первое слово не в нижнем регистре.
Вы можете проанализировать имена функций на предмет правильного использования заглавных букв, разбив слово, где происходит переход к регистру. Для каждой части исходного имени функции найдите это подслово в словаре или файле словаря + жаргон ("calc", "url", "admin" и т. Д. (Возможно, сначала проверьте жаргон)). Если какое-либо подслово терпит неудачу, то надлежащая капитализация не на месте.
Вы можете использовать Solr или ElasticSearch, чтобы разбить ваши слова на части с помощью WordDelimiterFilter в Lucene. Это создаст подслов при изменении регистра:
"PowerShot" -> "Power" "Shot"
"LoginURL" => "Login" "URL"
Вы можете либо вставить слова непосредственно в эти базы данных NoSQL и выполнить анализ позже, либо вы можете (по крайней мере, в ES) просто использовать фильтр токенов разделителя слов, чтобы разбить ваш запрос без фактического сохранения результатов.
https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters
Пример:
calcAdminLogin => calc Admin Войти
calcadminlogin => calcadminlogin
Если у вас есть дополнительный словарь, который содержит такие слова, как "calc" и "admin", то имя первой функции будет разложено на 3 слова, которые будут присутствовать в словаре, поэтому верблюд верен.
Во втором примере 'calcadminlogin' не будет найден в словаре, поэтому верблюжий регистр неверен.
Мое рекомендуемое регулярное выражение для проверки верблюжьего регистра, которое допускает однобуквенные слова, но запрещает аббревиатуры ALLCAPS:^[a-z]+(?:[A-Z](?:[A-Z]?[a-z]+|\d+|$)|\d+)*$
Разбивка: (Регулярное выражение101)
^ #start of the string
[a-z]+ #one or more lowercase letters
(?: #start non-capturing group1
[A-Z] #one uppercase letter
(?: #start non-capturing group2
[A-Z]?[a-z]+ #an optional uppercase letter followed by one or more lowercase letters
| #OR
\d+ #one or more digits
| #OR
$ #end of string
) #end non-capturing group2
| #OR
\d+ #one or more digits
)* #end non-capturing group1, repeat zero or more times
$ #end of string
С точки зрения непрофессионала, строка CamelCase:
- ДОЛЖЕН начинаться хотя бы с одной строчной буквы,
- МОЖЕТ содержать только строчные буквы, прописные буквы и цифры,
- МОЖЕТ содержать только строчные буквы, если состоит из одного слова,
- МОЖЕТ иметь однобуквенные «слова» в верхнем регистре,
- НЕ ДОЛЖНО иметь аббревиатуры, выраженные заглавными буквами,
- НЕ ДОЛЖНО иметь строчные буквы после первого слова, если только перед ним не стоит прописная буква.
- МОЖЕТ иметь заглавную букву, если это начало «слова» или слова, состоящего из одной буквы.
Код: (Демо)
$camelTests = [
'hereIsOne',
'andAnother',
'tryAThirdOne',
'kebab-case',
'getUuid',
'getUUID',
'snake_case',
'mysteryMethod1',
'printIf0Or1',
'itsAMeWario',
'acronymOMGInMiddle',
'DELIMITED_CONSTANT',
'a1AndA2',
'a1andA2',
'StudlyCase',
'FLOTUS',
'makePlanC',
'4Life',
'wakeInTheAM',
'code1B35F2',
'42',
'aB1c1De'
];
foreach ($camelTests as $test) {
printf(
"%s%s\n",
str_pad($test, 20),
preg_match('/^[a-z]+(?:[A-Z](?:[A-Z]?[a-z]+|\d+|$)|\d+)*$/', $test) ? 'PASS' : 'FAIL'
);
}
Выход:
hereIsOne PASS
andAnother PASS
tryAThirdOne PASS
kebab-case FAIL
getUuid PASS
getUUID FAIL
snake_case FAIL
mysteryMethod1 PASS
printIf0Or1 PASS
itsAMeWario PASS
acronymOMGInMiddle FAIL
DELIMITED_CONSTANT FAIL
a1AndA2 PASS
a1andA2 FAIL
StudlyCase FAIL
FLOTUS FAIL
makePlanC PASS
4Life FAIL
wakeInTheAM FAIL
code1B35F2 PASS
42 FAIL
aB1c1De FAIL
Если в вашем стиле CamelCase разрешить написание аббревиатур заглавными буквами, то схема будет менее сложной:/^[a-z]+(?:[A-Z][a-z]*|\d+)*$
Эти шаблоны можно использовать для обеспечения соблюдения согласованных и допустимых соглашений об именах CamelCase в вашем редакторе (например, PHPStorm).
PS Если вы хотите узнать другие паттерны в моих проектах...
- PHP StudlyCase/имена классов:
(?:[A-Z](?:[A-Z]?[a-z]+|\d+|$)|(?!^)\d+)+
- PHP-константы:
[A-Z]+(?:_(?:[A-Z]+|\d+))*