diff --git a/src/cdk-experimental/popover-edit/popover-edit.spec.ts b/src/cdk-experimental/popover-edit/popover-edit.spec.ts index 18c3d340f4f..3c12d842600 100644 --- a/src/cdk-experimental/popover-edit/popover-edit.spec.ts +++ b/src/cdk-experimental/popover-edit/popover-edit.spec.ts @@ -54,7 +54,7 @@ interface PeriodicElement { } abstract class BaseTestComponent { - @ViewChild('table') table: ElementRef; + @ViewChild('table', {static: false}) table: ElementRef; preservedValues = new Map(); diff --git a/src/material-experimental/mdc-checkbox/checkbox.spec.ts b/src/material-experimental/mdc-checkbox/checkbox.spec.ts index 6cdfe9b47e9..88c1ca33daf 100644 --- a/src/material-experimental/mdc-checkbox/checkbox.spec.ts +++ b/src/material-experimental/mdc-checkbox/checkbox.spec.ts @@ -1050,7 +1050,7 @@ class CheckboxWithTabIndex { `, }) class CheckboxUsingViewChild { - @ViewChild(MatCheckbox) checkbox: MatCheckbox; + @ViewChild(MatCheckbox, {static: false}) checkbox: MatCheckbox; set isDisabled(value: boolean) { this.checkbox.disabled = value; diff --git a/src/material-experimental/popover-edit/popover-edit.spec.ts b/src/material-experimental/popover-edit/popover-edit.spec.ts index 9d4de96d057..d6b3039af8d 100644 --- a/src/material-experimental/popover-edit/popover-edit.spec.ts +++ b/src/material-experimental/popover-edit/popover-edit.spec.ts @@ -58,7 +58,7 @@ interface PeriodicElement { } abstract class BaseTestComponent { - @ViewChild('table') table: ElementRef; + @ViewChild('table', {static: false}) table: ElementRef; preservedValues = new Map(); diff --git a/tools/tslint-rules/staticQueryRule.ts b/tools/tslint-rules/staticQueryRule.ts new file mode 100644 index 00000000000..34b420edf5e --- /dev/null +++ b/tools/tslint-rules/staticQueryRule.ts @@ -0,0 +1,38 @@ +import * as ts from 'typescript'; +import * as Lint from 'tslint'; + +/** + * Rule which enforces that all queries are explicitly marked as static or non-static. + */ +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); + } +} + +class Walker extends Lint.RuleWalker { + visitPropertyDeclaration(node: ts.PropertyDeclaration) { + const childQueryDecorator = node.decorators && node.decorators.find(decorator => { + const expression = (decorator.expression as ts.CallExpression); + const name = expression && expression.expression.getText(); + return name === 'ViewChild' || name === 'ContentChild'; + }); + + if (childQueryDecorator) { + const options = (childQueryDecorator.expression as ts.CallExpression).arguments[1]; + + if (!options || !ts.isObjectLiteralExpression(options) || + !this._getObjectProperty(options, 'static')) { + this.addFailureAtNode(childQueryDecorator, + 'Queries have to explicitly set the `static` option.'); + } + } + + super.visitPropertyDeclaration(node); + } + + /** Gets the node of an object property by name. */ + private _getObjectProperty(node: ts.ObjectLiteralExpression, name: string) { + return node.properties.find(property => (property.name as ts.Identifier).getText() === name); + } +} diff --git a/tslint.json b/tslint.json index 28d425438ad..20343782cf7 100644 --- a/tslint.json +++ b/tslint.json @@ -102,6 +102,7 @@ "ng-on-changes-property-access": true, "rxjs-imports": true, "require-breaking-change-version": true, + "static-query": true, "no-host-decorator-in-concrete": [ true, "HostBinding",