mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Check the migration files for table, column and index length errors
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
fe6e8c2710
commit
3b267b165f
3 changed files with 213 additions and 3 deletions
|
|
@ -97,7 +97,7 @@ class CheckCode extends Command implements CompletionAwareInterface {
|
|||
$checkList = new $checkerClass($checkList);
|
||||
}
|
||||
|
||||
$codeChecker = new CodeChecker($checkList);
|
||||
$codeChecker = new CodeChecker($checkList, !$input->getOption('skip-validate-info'));
|
||||
|
||||
$codeChecker->listen('CodeChecker', 'analyseFileBegin', function($params) use ($output) {
|
||||
if(OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
|
||||
|
|
|
|||
|
|
@ -51,8 +51,12 @@ class CodeChecker extends BasicEmitter {
|
|||
/** @var ICheck */
|
||||
protected $checkList;
|
||||
|
||||
public function __construct(ICheck $checkList) {
|
||||
/** @var bool */
|
||||
protected $checkMigrationSchema;
|
||||
|
||||
public function __construct(ICheck $checkList, $checkMigrationSchema) {
|
||||
$this->checkList = $checkList;
|
||||
$this->checkMigrationSchema = $checkMigrationSchema;
|
||||
$this->parser = new Parser(new Lexer);
|
||||
}
|
||||
|
||||
|
|
@ -120,11 +124,16 @@ class CodeChecker extends BasicEmitter {
|
|||
$statements = $this->parser->parse($code);
|
||||
|
||||
$visitor = new NodeVisitor($this->checkList);
|
||||
$migrationVisitor = new MigrationSchemaChecker();
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor($visitor);
|
||||
|
||||
if ($this->checkMigrationSchema && preg_match('#^.+\\/Migration\\/Version[^\\/]{1,255}\\.php$#i', $file)) {
|
||||
$traverser->addVisitor($migrationVisitor);
|
||||
}
|
||||
|
||||
$traverser->traverse($statements);
|
||||
|
||||
return $visitor->errors;
|
||||
return array_merge($visitor->errors, $migrationVisitor->errors);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
201
lib/private/App/CodeChecker/MigrationSchemaChecker.php
Normal file
201
lib/private/App/CodeChecker/MigrationSchemaChecker.php
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\App\CodeChecker;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class MigrationSchemaChecker extends NodeVisitorAbstract {
|
||||
|
||||
/** @var string */
|
||||
protected $schemaVariableName = null;
|
||||
/** @var array */
|
||||
protected $tableVariableNames = [];
|
||||
/** @var array */
|
||||
public $errors = [];
|
||||
|
||||
public function enterNode(Node $node) {
|
||||
/**
|
||||
* Check tables
|
||||
*/
|
||||
if ($this->schemaVariableName !== null &&
|
||||
$node instanceof Node\Expr\Assign &&
|
||||
$node->var instanceof Node\Expr\Variable &&
|
||||
$node->expr instanceof Node\Expr\MethodCall &&
|
||||
$node->expr->var instanceof Node\Expr\Variable &&
|
||||
$node->expr->var->name === $this->schemaVariableName) {
|
||||
|
||||
if ($node->expr->name === 'createTable') {
|
||||
if (isset($node->expr->args[0]) && $node->expr->args[0]->value instanceof Node\Scalar\String_) {
|
||||
if (!$this->checkNameLength($node->expr->args[0]->value->value)) {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => $node->expr->args[0]->value->value,
|
||||
'reason' => 'Table name is too long (max. 27)',
|
||||
];
|
||||
} else {
|
||||
$this->tableVariableNames[$node->var->name] = $node->expr->args[0]->value->value;
|
||||
}
|
||||
}
|
||||
} else if ($node->expr->name === 'getTable') {
|
||||
if (isset($node->expr->args[0]) && $node->expr->args[0]->value instanceof Node\Scalar\String_) {
|
||||
$this->tableVariableNames[$node->var->name] = $node->expr->args[0]->value->value;
|
||||
}
|
||||
}
|
||||
} else if ($this->schemaVariableName !== null &&
|
||||
$node instanceof Node\Expr\MethodCall &&
|
||||
$node->var instanceof Node\Expr\Variable &&
|
||||
$node->var->name === $this->schemaVariableName) {
|
||||
|
||||
if ($node->name === 'renameTable') {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => 'Deprecated method',
|
||||
'reason' => sprintf(
|
||||
'`$%s->renameTable()` must not be used',
|
||||
$node->var->name
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check columns and Indexes
|
||||
*/
|
||||
} else if (!empty($this->tableVariableNames) &&
|
||||
$node instanceof Node\Expr\MethodCall &&
|
||||
$node->var instanceof Node\Expr\Variable &&
|
||||
isset($this->tableVariableNames[$node->var->name])) {
|
||||
|
||||
if ($node->name === 'addColumn' || $node->name === 'changeColumn') {
|
||||
if (isset($node->args[0]) && $node->args[0]->value instanceof Node\Scalar\String_) {
|
||||
if (!$this->checkNameLength($node->args[0]->value->value)) {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => $node->args[0]->value->value,
|
||||
'reason' => sprintf(
|
||||
'Column name is too long on table `%s` (max. 27)',
|
||||
$this->tableVariableNames[$node->var->name]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// On autoincrement the max length of the table name is 21 instead of 27
|
||||
if (isset($node->args[2]) && $node->args[2]->value instanceof Node\Expr\Array_) {
|
||||
/** @var Node\Expr\Array_ $options */
|
||||
$options = $node->args[2]->value;
|
||||
if ($this->checkColumnForAutoincrement($options)) {
|
||||
if (!$this->checkNameLength($this->tableVariableNames[$node->var->name], true)) {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => $this->tableVariableNames[$node->var->name],
|
||||
'reason' => 'Table name is too long because of autoincrement (max. 21)',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($node->name === 'addIndex' ||
|
||||
$node->name === 'addUniqueIndex' ||
|
||||
$node->name === 'renameIndex' ||
|
||||
$node->name === 'setPrimaryKey') {
|
||||
if (isset($node->args[1]) && $node->args[1]->value instanceof Node\Scalar\String_) {
|
||||
if (!$this->checkNameLength($node->args[1]->value->value)) {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => $node->args[1]->value->value,
|
||||
'reason' => sprintf(
|
||||
'Index name is too long on table `%s` (max. 27)',
|
||||
$this->tableVariableNames[$node->var->name]
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
} else if ($node->name === 'addForeignKeyConstraint') {
|
||||
if (isset($node->args[4]) && $node->args[4]->value instanceof Node\Scalar\String_) {
|
||||
if (!$this->checkNameLength($node->args[4]->value->value)) {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => $node->args[4]->value->value,
|
||||
'reason' => sprintf(
|
||||
'Constraint name is too long on table `%s` (max. 27)',
|
||||
$this->tableVariableNames[$node->var->name]
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
} else if ($node->name === 'renameColumn') {
|
||||
$this->errors[] = [
|
||||
'line' => $node->getLine(),
|
||||
'disallowedToken' => 'Deprecated method',
|
||||
'reason' => sprintf(
|
||||
'`$%s->renameColumn()` must not be used',
|
||||
$node->var->name
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the schema
|
||||
*/
|
||||
} else if ($node instanceof Node\Expr\Assign &&
|
||||
$node->expr instanceof Node\Expr\FuncCall &&
|
||||
$node->var instanceof Node\Expr\Variable &&
|
||||
$node->expr->name instanceof Node\Expr\Variable &&
|
||||
$node->expr->name->name === 'schemaClosure') {
|
||||
// E.g. $schema = $schemaClosure();
|
||||
$this->schemaVariableName = $node->var->name;
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkNameLength($tableName, $hasAutoincrement = false) {
|
||||
if ($hasAutoincrement) {
|
||||
return strlen($tableName) <= 21;
|
||||
}
|
||||
return strlen($tableName) <= 27;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node\Expr\Array_ $optionsArray
|
||||
* @return bool Whether the column is an autoincrement column
|
||||
*/
|
||||
protected function checkColumnForAutoincrement(Node\Expr\Array_ $optionsArray) {
|
||||
foreach ($optionsArray->items as $option) {
|
||||
if ($option->key instanceof Node\Scalar\String_) {
|
||||
if ($option->key->value === 'autoincrement' &&
|
||||
$option->value instanceof Node\Expr\ConstFetch) {
|
||||
/** @var Node\Expr\ConstFetch $const */
|
||||
$const = $option->value;
|
||||
|
||||
if ($const->name instanceof Name &&
|
||||
$const->name->parts === ['true']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue