This repository has been archived by the owner on Jul 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
/
noJqueryRawElementsRule.ts
98 lines (84 loc) · 3.64 KB
/
noJqueryRawElementsRule.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import * as ts from 'typescript';
import * as Lint from 'tslint';
import {AstUtils} from './utils/AstUtils';
import {ExtendedMetadata} from './utils/ExtendedMetadata';
const FAILURE_STRING_MANIPULATION: string = 'Replace HTML string manipulation with jQuery API: ';
const FAILURE_STRING_COMPLEX: string = 'Replace complex HTML strings with jQuery API: ';
/**
* Implementation of the no-jquery-raw-elements rule.
*/
export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'no-jquery-raw-elements',
type: 'maintainability',
description: 'Do not create HTML elements using JQuery and string concatenation. It is error prone and can hide subtle defects.',
options: null,
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Important',
level: 'Opportunity for Excellence',
group: 'Correctness',
commonWeaknessEnumeration: '398, 710'
};
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NoJqueryRawElementsRuleWalker(sourceFile, this.getOptions()));
}
}
class NoJqueryRawElementsRuleWalker extends Lint.RuleWalker {
protected visitCallExpression(node: ts.CallExpression): void {
const functionName: string = AstUtils.getFunctionName(node);
if (AstUtils.isJQuery(functionName) && node.arguments.length > 0) {
const firstArg: ts.Expression = node.arguments[0];
if (firstArg.kind === ts.SyntaxKind.StringLiteral) {
if (this.isComplexHtmlElement(<ts.StringLiteral>firstArg)) {
this.addFailureAt(node.getStart(), node.getWidth(), FAILURE_STRING_COMPLEX + node.getText());
}
} else {
const finder = new HtmlLikeStringLiteralFinder(this.getSourceFile(), this.getOptions());
finder.walk(node.arguments[0]);
if (finder.isFound()) {
this.addFailureAt(node.getStart(), node.getWidth(), FAILURE_STRING_MANIPULATION + node.getText());
}
}
}
super.visitCallExpression(node);
}
private isComplexHtmlElement(literal: ts.StringLiteral): boolean {
const text: string = literal.text.trim();
if (/^<.*>$/.test(text) === false) {
// element does not look like an html tag
return false;
}
if (/^<[A-Za-z]+\s*\/?>$/.test(text) === true) {
// element looks like a simple self-closing tag or a simple opening tag
return false;
}
if (/^<[A-Za-z]+\s*>\s*<\/[A-Za-z]+\s*>$/m.test(text) === true) {
// element looks like a simple open and closed tag
return false;
}
const match = text.match(/^<[A-Za-z]+\s*>(.*)<\/[A-Za-z]+\s*>$/m);
if (match != null && match[1] != null) {
const enclosedContent: string = match[1]; // get the stuff inside the tag
if (enclosedContent.indexOf('<') === -1 && enclosedContent.indexOf('>') === -1) {
return false; // enclosed content looks like it contains no html elements
}
}
return true;
}
}
class HtmlLikeStringLiteralFinder extends Lint.RuleWalker {
private found: boolean = false;
public isFound(): boolean {
return this.found;
}
protected visitStringLiteral(node: ts.StringLiteral): void {
if (node.text.indexOf('<') > -1 || node.text.indexOf('>') > -1) {
this.found = true;
} else {
super.visitStringLiteral(node);
}
}
}