1 | import * as ts from 'typescript';
|
2 | import * as Lint from 'tslint';
|
3 |
|
4 | import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
|
5 | import { ExtendedMetadata } from './utils/ExtendedMetadata';
|
6 | import { forEachTokenWithTrivia } from 'tsutils';
|
7 |
|
8 | const FAILURE_STRING: string = 'No commented out code.';
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | export class Rule extends Lint.Rules.AbstractRule {
|
14 | public static metadata: ExtendedMetadata = {
|
15 | ruleName: 'no-commented-out-code',
|
16 | type: 'maintainability',
|
17 | description: 'Code must not be commented out.',
|
18 | options: null,
|
19 | optionsDescription: '',
|
20 | optionExamples: [],
|
21 | typescriptOnly: false,
|
22 | issueClass: 'Non-SDL',
|
23 | issueType: 'Warning',
|
24 | severity: 'Low',
|
25 | level: 'Opportunity for Excellence',
|
26 | group: 'Clarity',
|
27 | commonWeaknessEnumeration: '',
|
28 | };
|
29 |
|
30 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
31 | return this.applyWithWalker(new NoCommentedOutCodeRuleWalker(sourceFile, this.getOptions()));
|
32 | }
|
33 | }
|
34 |
|
35 | class NoCommentedOutCodeRuleWalker extends ErrorTolerantWalker {
|
36 | public visitSourceFile(node: ts.SourceFile) {
|
37 | forEachTokenWithTrivia(node, (text, tokenSyntaxKind, range) => {
|
38 | if (tokenSyntaxKind === ts.SyntaxKind.SingleLineCommentTrivia || tokenSyntaxKind === ts.SyntaxKind.MultiLineCommentTrivia) {
|
39 | this.scanCommentForCode(range.pos, text.substring(range.pos, range.end));
|
40 | }
|
41 | });
|
42 | }
|
43 |
|
44 | private scanCommentForCode(startPosition: number, commentText: string) {
|
45 | const trimmedCommentText = this.trimTextLines(commentText);
|
46 |
|
47 | if (this.isTextCode(trimmedCommentText)) {
|
48 | this.handleSuspiciousComment(startPosition, commentText);
|
49 | }
|
50 | }
|
51 |
|
52 | |
53 |
|
54 |
|
55 |
|
56 | private trimTextLines(text: string): string {
|
57 | const lines = text.split('\n');
|
58 |
|
59 | const trimAtStart = /^\s*\/*\**\s*/;
|
60 | const trimAtEnd = /\s*\**\/*\s*$/;
|
61 |
|
62 | const trimmedLines = lines.map(line => {
|
63 | return line.replace(trimAtStart, '').replace(trimAtEnd, '');
|
64 | });
|
65 |
|
66 | return trimmedLines.join('\n');
|
67 | }
|
68 |
|
69 | private isTextCode(text: string): boolean {
|
70 | if (this.isTextSingleWord(text)) {
|
71 | return false;
|
72 | }
|
73 | if (this.isTextTsLintFlag(text)) {
|
74 | return false;
|
75 | }
|
76 | if (this.isTextToDoLikeNote(text)) {
|
77 | return false;
|
78 | }
|
79 |
|
80 | return this.isTextCodeWithoutErrors(text);
|
81 | }
|
82 |
|
83 | private isTextSingleWord(text: string): boolean {
|
84 | const pattern = new RegExp('^([\\w-]*)$');
|
85 | return pattern.test(text.trim());
|
86 | }
|
87 |
|
88 | |
89 |
|
90 |
|
91 |
|
92 | private isTextTsLintFlag(text: string): boolean {
|
93 | return text.startsWith('tslint:');
|
94 | }
|
95 |
|
96 | |
97 |
|
98 |
|
99 |
|
100 | private isTextToDoLikeNote(text: string): boolean {
|
101 | return /^(NOTE|TODO|FIXME|BUG|HACK|XXX):/.test(text);
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 | private isTextCodeWithoutErrors(text: string) {
|
108 | const sourceFile = this.createSourceFileFromText(text);
|
109 |
|
110 | if (!this.hasSourceFileStatements(sourceFile)) {
|
111 | return false;
|
112 | }
|
113 |
|
114 | const sourceFileDiagnostics = this.getSourceFileDiagnostics(sourceFile);
|
115 |
|
116 | return sourceFileDiagnostics.length === 0;
|
117 | }
|
118 |
|
119 | private createSourceFileFromText(text: string): ts.SourceFile {
|
120 | return ts.createSourceFile('', text, ts.ScriptTarget.ES5, true);
|
121 | }
|
122 |
|
123 | private hasSourceFileStatements(sourceFile: ts.SourceFile): boolean {
|
124 | return sourceFile && sourceFile.statements.length > 0;
|
125 | }
|
126 |
|
127 | |
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | private getSourceFileDiagnostics(sourceFile: ts.SourceFile): ts.Diagnostic[] {
|
135 | return (<any>sourceFile).parseDiagnostics;
|
136 | }
|
137 |
|
138 | private handleSuspiciousComment(startPosition: number, commentText: string) {
|
139 | this.addFailureAt(startPosition, commentText.length, FAILURE_STRING);
|
140 | }
|
141 | }
|