import * as ts from 'typescript' ; import * as Lint from 'tslint' ; import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker' ; import { ExtendedMetadata } from './utils/ExtendedMetadata' ; import { forEachTokenWithTrivia } from 'tsutils' ; const FAILURE_STRING = 'No commented out code.' ; /**
 * Implementation of the no-commented-out-code rule.
 */ export class Rule extends Lint . Rules . AbstractRule { public static metadata : ExtendedMetadata = { ruleName : 'no-commented-out-code' , type : 'maintainability' , description : 'Code must not be commented out.' , options : null , optionsDescription : '' , optionExamples : [ ] , typescriptOnly : false , issueClass : 'Non-SDL' , issueType : 'Warning' , severity : 'Low' , level : 'Opportunity for Excellence' , group : 'Clarity' , commonWeaknessEnumeration : '' , } ; public apply ( sourceFile ) { return this . applyWithWalker ( new NoCommentedOutCodeRuleWalker ( sourceFile , this . getOptions ( ) ) ) ; } } class NoCommentedOutCodeRuleWalker extends ErrorTolerantWalker { public visitSourceFile ( node ) { forEachTokenWithTrivia ( node , ( text , tokenSyntaxKind , range ) => { if ( tokenSyntaxKind === ts . SyntaxKind . SingleLineCommentTrivia || tokenSyntaxKind === ts . SyntaxKind . MultiLineCommentTrivia ) { this . scanCommentForCode ( range . pos , text . substring ( range . pos , range . end ) ) ; } } ) ; } private scanCommentForCode ( startPosition , commentText ) { const trimmedCommentText = this . trimTextLines ( commentText ) ; if ( this . isTextCode ( trimmedCommentText ) ) { this . handleSuspiciousComment ( startPosition , commentText ) ; } } /**
     * Removes spaces and comment delimiters at beginning
     * and end of each line
     */ private trimTextLines ( text ) { const lines = text . split ( '\n' ) ; const trimAtStart = /^\s*\/*\**\s*/ ; const trimAtEnd = /\s*\**\/*\s*$/ ; const trimmedLines = lines . map ( line => { return line . replace ( trimAtStart , '' ) . replace ( trimAtEnd , '' ) ; } ) ; return trimmedLines . join ( '\n' ) ; } private isTextCode ( text ) { if ( this . isTextSingleWord ( text ) ) { return false ; } if ( this . isTextTsLintFlag ( text ) ) { return false ; } if ( this . isTextToDoLikeNote ( text ) ) { return false ; } return this . isTextCodeWithoutErrors ( text ) ; } private isTextSingleWord ( text ) { const pattern = new RegExp ( '^([\\w-]*)$' ) ; return pattern . test ( text . trim ( ) ) ; } /**
     * TSLint flags will be will be parsed as labeled statements and thus
     * may result in valid code, so they need to be handled separately
     */ private isTextTsLintFlag ( text ) { return text . startsWith ( 'tslint:' ) ; } /**
     * These notes followed by a colon will be parsed as labeled statements
     * and thus may result in valid code, so they need to be handled separately
     */ private isTextToDoLikeNote ( text ) { return /^(NOTE|TODO|FIXME|BUG|HACK|XXX):/ . test ( text ) ; } /**
     * If text contains statements but not one error, it must be code
     */ private isTextCodeWithoutErrors ( text ) { const sourceFile = this . createSourceFileFromText ( text ) ; if ( ! this . hasSourceFileStatements ( sourceFile ) ) { return false ; } const sourceFileDiagnostics = this . getSourceFileDiagnostics ( sourceFile ) ; return sourceFileDiagnostics . length === 0 ; } private createSourceFileFromText ( text ) { return ts . createSourceFile ( '' , text , ts . ScriptTarget . ES5 , true ) ; } private hasSourceFileStatements ( sourceFile ) { return sourceFile && sourceFile . statements . length > 0 ; } see https private getSourceFileDiagnostics ( sourceFile ) { return ( < any > sourceFile ) . parseDiagnostics ; } private handleSuspiciousComment ( startPosition , commentText ) { this . addFailureAt ( startPosition , commentText . length , FAILURE_STRING ) ; } }