Skip to content

Commit dc04596

Browse files
crisbetoandrewseguin
authored andcommitted
build: expand validateDecoratorsRule to support blacklisting (#8897)
Expands the decorator validation rule to support blacklisting properties whose values match a pattern. This allows disabling things like inline styles.
1 parent f5a2faf commit dc04596

File tree

2 files changed

+44
-14
lines changed

2 files changed

+44
-14
lines changed

tools/tslint-rules/validateDecoratorsRule.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import * as minimatch from 'minimatch';
55

66
/**
77
* Rule that enforces certain decorator properties to be defined and to match a pattern.
8-
* Supports whitelisting via the third argument. E.g.
8+
* Properties can be forbidden by prefixing their name with a `!`.
9+
* Supports whitelisting files via the third argument. E.g.
910
*
1011
* ```
1112
* "validate-decorators": [true, {
1213
* "Component": {
13-
* "encapsulation": "\\.None$"
14+
* "encapsulation": "\\.None$",
15+
* "!styles": ".*"
1416
* }
1517
* }, "src/lib"]
1618
* ```
@@ -21,8 +23,16 @@ export class Rule extends Lint.Rules.AbstractRule {
2123
}
2224
}
2325

24-
/** Rules that can be used to validate the decorators in a file. */
25-
type DecoratorRules = {[key: string]: {[key: string]: RegExp}};
26+
/** Represents a set of required and forbidden decorator properties. */
27+
type DecoratorRuleSet = {
28+
required: {[key: string]: RegExp},
29+
forbidden: {[key: string]: RegExp},
30+
};
31+
32+
/** Represents a map between decorator names and rule sets. */
33+
type DecoratorRules = {
34+
[key: string]: DecoratorRuleSet
35+
};
2636

2737
class Walker extends Lint.RuleWalker {
2838
// Whether the file should be checked at all.
@@ -76,20 +86,30 @@ class Walker extends Lint.RuleWalker {
7686
node
7787
}));
7888

79-
// Find all of the rule properties that are missing from the decorator.
80-
const missing = Object.keys(rules).filter(key => !props.find((prop: any) => prop.name === key));
89+
// Find all of the required rule properties that are missing from the decorator.
90+
const missing = Object.keys(rules.required)
91+
.filter(key => !props.find((prop: any) => prop.name === key));
8192

8293
if (missing.length) {
8394
// Exit early if any of the properties are missing.
8495
this.addFailureAtNode(decorator.parent, 'Missing required properties: ' + missing.join(', '));
8596
} else {
86-
// If all the necessary properties are defined, ensure that they match the pattern.
97+
// If all the necessary properties are defined, ensure that
98+
// they match the pattern and aren't in the forbidden list.
8799
props
88-
.filter((prop: any) => rules[prop.name])
89-
.filter((prop: any) => !rules[prop.name].test(prop.value))
100+
.filter((prop: any) => rules.required[prop.name] || rules.forbidden[prop.name])
90101
.forEach((prop: any) => {
91-
this.addFailureAtNode(prop.node,
92-
`Invalid value for property. Expected value to match "${rules[prop.name]}".`);
102+
const {name, value, node} = prop;
103+
const requiredPattern = rules.required[name];
104+
const forbiddenPattern = rules.forbidden[name];
105+
106+
if (requiredPattern && !requiredPattern.test(value)) {
107+
this.addFailureAtNode(node, `Invalid value for property. ` +
108+
`Expected value to match "${requiredPattern}".`);
109+
} else if (forbiddenPattern && forbiddenPattern.test(value)) {
110+
this.addFailureAtNode(node, `Property value not allowed. ` +
111+
`Value should not match "${forbiddenPattern}".`);
112+
}
93113
});
94114
}
95115
}
@@ -108,9 +128,18 @@ class Walker extends Lint.RuleWalker {
108128
.filter(decoratorName => Object.keys(config[decoratorName]).length > 0)
109129
.forEach(decoratorName => {
110130
output[decoratorName] = Object.keys(config[decoratorName]).reduce((accumulator, prop) => {
111-
accumulator[prop] = new RegExp(config[decoratorName][prop]);
131+
const isForbidden = prop.startsWith('!');
132+
const cleanName = isForbidden ? prop.slice(1) : prop;
133+
const pattern = new RegExp(config[decoratorName][prop]);
134+
135+
if (isForbidden) {
136+
accumulator.forbidden[cleanName] = pattern;
137+
} else {
138+
accumulator.required[cleanName] = pattern;
139+
}
140+
112141
return accumulator;
113-
}, {} as {[key: string]: RegExp});
142+
}, {required: {}, forbidden: {}} as DecoratorRuleSet);
114143
});
115144
}
116145

tslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@
9696
"encapsulation": "\\.None$",
9797
"moduleId": "^module\\.id$",
9898
"preserveWhitespaces": "false$",
99-
"changeDetection": "\\.OnPush$"
99+
"changeDetection": "\\.OnPush$",
100+
"!styles": ".*"
100101
}
101102
}, "src/+(lib|cdk|material-experimental)/**/!(*.spec).ts"],
102103
"require-license-banner": [

0 commit comments

Comments
 (0)