Skip to content

Commit 1cc3a79

Browse files
crisbetommalerba
authored andcommitted
build: add linting for classList usage (#17388)
Once in a while we hit an issue because we use some of the `classList` methods in a way that isn't supported in all browsers (e.g. passing multiple parameters to `add` in #17378). These changes add a simple lint rule to catch these cases earlier.
1 parent b8b0554 commit 1cc3a79

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

src/material-experimental/mdc-button/button-base.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,15 @@ export class MatButtonBase extends _MatButtonBaseMixin implements CanDisable, Ca
103103
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) {
104104
super(elementRef);
105105

106+
const classList = (elementRef.nativeElement as HTMLElement).classList;
107+
106108
// For each of the variant selectors that is present in the button's host
107109
// attributes, add the correct corresponding MDC classes.
108110
for (const pair of HOST_SELECTOR_MDC_CLASS_PAIR) {
109111
if (this._hasHostAttributes(pair.selector)) {
110-
(elementRef.nativeElement as HTMLElement).classList.add(...pair.mdcClasses);
112+
pair.mdcClasses.forEach(className => {
113+
classList.add(className);
114+
});
111115
}
112116
}
113117
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as ts from 'typescript';
2+
import * as Lint from 'tslint';
3+
4+
/**
5+
* Rule that catches cases where `classList` is used in a way
6+
* that won't work in all browsers that we support.
7+
*/
8+
export class Rule extends Lint.Rules.TypedRule {
9+
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
10+
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
11+
}
12+
}
13+
14+
class Walker extends Lint.ProgramAwareRuleWalker {
15+
visitPropertyAccessExpression(propertyAccess: ts.PropertyAccessExpression) {
16+
const parent = propertyAccess.parent;
17+
18+
// We only care about property accesses inside of calls.
19+
if (!ts.isCallExpression(parent)) {
20+
return;
21+
}
22+
23+
// We only care about these method names.
24+
const name = propertyAccess.name.text;
25+
if (name !== 'add' && name !== 'remove' && name !== 'toggle' && name !== 'replace') {
26+
return;
27+
}
28+
29+
const symbol = this.getTypeChecker().getTypeAtLocation(propertyAccess.expression).symbol;
30+
31+
if (symbol && symbol.name === 'DOMTokenList') {
32+
const args = parent.arguments;
33+
34+
if (name === 'replace') {
35+
this.addFailureAtNode(propertyAccess,
36+
'This method is not supported in iOS Safari. Use `add` and `remove` instead.');
37+
} else if (args.length > 1 || (args.length === 1 && ts.isSpreadElement(args[0]))) {
38+
this.addFailureAtNode(propertyAccess,
39+
'Passing in multiple arguments into this method is not supported in some browsers. ' +
40+
'Use the single argument signature instead.');
41+
}
42+
}
43+
44+
super.visitPropertyAccessExpression(propertyAccess);
45+
}
46+
}

tslint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"rxjs-imports": true,
108108
"require-breaking-change-version": true,
109109
"static-query": true,
110+
"class-list-signatures": true,
110111
"no-host-decorator-in-concrete": [
111112
true,
112113
"HostBinding",

0 commit comments

Comments
 (0)