diff options
author | Joas Schilling <coding@schilljs.com> | 2017-06-09 14:20:44 +0200 |
---|---|---|
committer | Morris Jobke <hey@morrisjobke.de> | 2017-07-05 13:02:16 +0200 |
commit | 3b267b165f738c50d375a5e988d4ca1892019eb5 (patch) | |
tree | 4d11f0f10a71e9edc27697fa8def72566b75c765 /lib/private/App | |
parent | fe6e8c2710a4df0aedc9dd4d2323ed009a53a763 (diff) | |
download | nextcloud-server-3b267b165f738c50d375a5e988d4ca1892019eb5.tar.gz nextcloud-server-3b267b165f738c50d375a5e988d4ca1892019eb5.zip |
Check the migration files for table, column and index length errors
Signed-off-by: Joas Schilling <coding@schilljs.com>
Diffstat (limited to 'lib/private/App')
-rw-r--r-- | lib/private/App/CodeChecker/CodeChecker.php | 13 | ||||
-rw-r--r-- | lib/private/App/CodeChecker/MigrationSchemaChecker.php | 201 |
2 files changed, 212 insertions, 2 deletions
diff --git a/lib/private/App/CodeChecker/CodeChecker.php b/lib/private/App/CodeChecker/CodeChecker.php index 291bedee92f..a6368ab683f 100644 --- a/lib/private/App/CodeChecker/CodeChecker.php +++ b/lib/private/App/CodeChecker/CodeChecker.php @@ -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); } } diff --git a/lib/private/App/CodeChecker/MigrationSchemaChecker.php b/lib/private/App/CodeChecker/MigrationSchemaChecker.php new file mode 100644 index 00000000000..9dee358327d --- /dev/null +++ b/lib/private/App/CodeChecker/MigrationSchemaChecker.php @@ -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; + } +} |