Skip to content

Commit c306297

Browse files
julianobrasiljelbourn
authored andcommitted
build: tslint rule to enforce html tags escaping in comments (#8200)
1 parent 8df7389 commit c306297

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as ts from 'typescript';
2+
import * as Lint from 'tslint';
3+
import * as utils from 'tsutils';
4+
5+
const ERROR_MESSAGE =
6+
'An HTML tag delimiter (< or >) may only appear in a JSDoc comment if it is escaped.' +
7+
' This prevents failures in document generation caused by a misinterpreted tag.';
8+
9+
/**
10+
* Rule that walks through all comments inside of the library and adds failures when it
11+
* detects unescaped HTML tags inside of multi-line comments.
12+
*/
13+
export class Rule extends Lint.Rules.AbstractRule {
14+
15+
apply(sourceFile: ts.SourceFile) {
16+
return this.applyWithWalker(new NoUnescapedHtmlTagWalker(sourceFile, this.getOptions()));
17+
}
18+
}
19+
20+
class NoUnescapedHtmlTagWalker extends Lint.RuleWalker {
21+
22+
visitSourceFile(sourceFile: ts.SourceFile) {
23+
utils.forEachComment(sourceFile, (fullText, commentRange) => {
24+
const htmlIsEscaped =
25+
this._parseForHtml(fullText.substring(commentRange.pos, commentRange.end));
26+
if (commentRange.kind === ts.SyntaxKind.MultiLineCommentTrivia && !htmlIsEscaped) {
27+
this.addFailureAt(commentRange.pos, commentRange.end - commentRange.pos, ERROR_MESSAGE);
28+
}
29+
});
30+
31+
super.visitSourceFile(sourceFile);
32+
}
33+
34+
/** Gets whether the comment's HTML, if any, is properly escaped */
35+
private _parseForHtml(fullText: string): boolean {
36+
const matches = /[<>]/;
37+
const backtickCount = fullText.split('`').length - 1;
38+
39+
// An odd number of backticks or html without backticks is invalid
40+
if (backtickCount % 2 || (!backtickCount && matches.test(fullText))) {
41+
return false;
42+
}
43+
44+
// Text without html is valid
45+
if (!matches.test(fullText)) {
46+
return true;
47+
}
48+
49+
// < and > must always be between two matching backticks.
50+
51+
// Whether an opening backtick has been found without a closing pair
52+
let openBacktick = false;
53+
54+
for (const char of fullText) {
55+
if (char === '`') {
56+
openBacktick = !openBacktick;
57+
} else if (matches.test(char) && !openBacktick) {
58+
return false;
59+
}
60+
}
61+
62+
return true;
63+
}
64+
}

tslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
true,
120120
"./tools/package-tools/rollup-globals.ts",
121121
"src/+(lib|cdk|material-examples|material-experimental|cdk-experimental)/**/*.ts"
122-
]
122+
],
123+
"no-unescaped-html-tag": true
123124
}
124125
}

0 commit comments

Comments
 (0)