From da0f553fe7d580f38907f9baa9124cbc55e37c96 Mon Sep 17 00:00:00 2001 From: Rafael Santana Date: Wed, 30 May 2018 11:12:37 -0300 Subject: [PATCH] chore: enable strict (#631) --- build/buildDocs.ts | 2 +- docs/src/app-linter/html-formatter.ts | 2 +- docs/src/app-linter/linter.ts | 5 +- docs/src/app-linter/plain-reporter.ts | 4 +- docs/src/app.ts | 2 +- docs/src/worker/web-linter.ts | 13 +- src/angular/config.ts | 70 ++++--- src/angular/metadata.ts | 29 ++- src/angular/metadataReader.ts | 106 +++++------ src/angular/ngWalker.ts | 144 +++++++-------- src/angular/sourceMappingVisitor.ts | 2 +- src/angular/styles/cssAst.ts | 2 +- src/angular/styles/cssLexer.ts | 22 +-- src/angular/styles/cssParser.ts | 173 +++++++++--------- .../templates/basicTemplateAstVisitor.ts | 30 +-- .../recursiveAngularExpressionVisitor.ts | 2 +- .../templates/referenceCollectorVisitor.ts | 4 +- src/angular/templates/templateParser.ts | 12 +- src/angular/urlResolvers/abstractResolver.ts | 63 +++---- src/angular/urlResolvers/pathResolver.ts | 2 +- src/angular/urlResolvers/urlResolver.ts | 26 +-- src/angularWhitespaceRule.ts | 9 +- src/bananaInBoxRule.ts | 3 +- src/componentClassSuffixRule.ts | 21 ++- src/contextualLifeCycleRule.ts | 10 +- src/decoratorNotAllowedRule.ts | 4 +- src/directiveClassSuffixRule.ts | 25 +-- src/enforceComponentSelectorRule.ts | 3 +- src/i18nRule.ts | 4 +- src/importDestructuringSpacingRule.ts | 4 +- src/maxInlineDeclarationsRule.ts | 79 +++----- src/noAttributeParameterDecoratorRule.ts | 54 +++--- src/noConflictingLifeCycleHooksRule.ts | 6 +- src/noForwardRefRule.ts | 38 ++-- src/noInputRenameRule.ts | 2 +- src/noOutputNamedAfterStandardEventRule.ts | 11 +- src/noOutputRenameRule.ts | 2 +- src/noQueriesParameterRule.ts | 12 +- src/noTemplateCallExpressionRule.ts | 4 +- src/noUnusedCssRule.ts | 32 ++-- src/pipeImpureRule.ts | 5 +- src/pipeNamingRule.ts | 13 +- src/preferInlineDecoratorRule.ts | 6 +- src/propertyDecoratorBase.ts | 7 +- src/selectorNameBase.ts | 13 +- src/templateConditionalComplexityRule.ts | 4 +- src/templatesNoNegatedAsyncRule.ts | 4 +- src/useHostPropertyDecoratorRule.ts | 20 +- src/useInputPropertyDecoratorRule.ts | 12 +- src/useLifeCycleInterfaceRule.ts | 36 ++-- src/useOutputPropertyDecoratorRule.ts | 12 +- src/usePipeDecoratorRule.ts | 74 ++++---- src/usePipeTransformInterfaceRule.ts | 75 ++++---- src/util/astQuery.ts | 59 ++---- src/util/classDeclarationUtils.ts | 18 +- src/util/function.ts | 40 ++-- src/util/ngQuery.ts | 28 +-- src/util/selectorValidator.ts | 21 ++- src/util/utils.ts | 41 +++-- src/walkerFactory/walkerFactory.ts | 4 +- src/walkerFactory/walkerFn.ts | 10 +- test/angular/basicCssAstVisitor.spec.ts | 14 +- test/angular/metadataReader.spec.ts | 74 ++++---- test/angular/ngWalker.spec.ts | 84 ++++----- test/angular/sourceMappingVisitor.spec.ts | 6 +- test/angular/urlResolvers/urlResolver.spec.ts | 28 +-- test/angularWhitespaceRule.spec.ts | 5 - test/contextualLifeCycleRule.spec.ts | 2 +- test/i18nRule.spec.ts | 12 +- test/importDestructuringSpacingRule.spec.ts | 8 +- test/maxInlineDeclarationsRule.spec.ts | 2 +- test/noInputPrefixRule.spec.ts | 1 - test/noTemplateCallExpressionRule.spec.ts | 2 - test/noUnusedCssRule.spec.ts | 15 +- test/preferInlineDecoratorRule.spec.ts | 4 +- test/testHelper.ts | 135 ++++++++------ test/util/classDeclarationUtils.spec.ts | 8 +- test/utils.ts | 18 +- tsconfig.json | 1 + 79 files changed, 923 insertions(+), 1036 deletions(-) diff --git a/build/buildDocs.ts b/build/buildDocs.ts index 08f2d9ccd..603707906 100644 --- a/build/buildDocs.ts +++ b/build/buildDocs.ts @@ -39,7 +39,7 @@ import * as path from 'path'; import { IFormatterMetadata, IRuleMetadata } from 'tslint'; -type Metadata = IRuleMetadata | IFormatterMetadata; +type Metadata = IRuleMetadata | IFormatterMetadata | undefined; interface Documented { metadata: Metadata; diff --git a/docs/src/app-linter/html-formatter.ts b/docs/src/app-linter/html-formatter.ts index c727a7a11..479563a46 100644 --- a/docs/src/app-linter/html-formatter.ts +++ b/docs/src/app-linter/html-formatter.ts @@ -14,7 +14,7 @@ export class HtmlFormatter implements Formatter { } private linkify(inputText: string) { - let replacedText: string, replacePattern1: RegExp, replacePattern2: RegExp, replacePattern3: RegExp; + let replacedText: string, replacePattern1: RegExp, replacePattern2: RegExp; // URLs starting with http://, https://, or ftp:// replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; diff --git a/docs/src/app-linter/linter.ts b/docs/src/app-linter/linter.ts index 88c513840..0b8140245 100644 --- a/docs/src/app-linter/linter.ts +++ b/docs/src/app-linter/linter.ts @@ -1,6 +1,4 @@ -import { Formatter } from './formatter-interface'; import { RichEditor } from './rich-editor-interface'; -import { Reporter } from './reporter-interface'; export interface LinterConfig { textEditor: RichEditor; @@ -9,8 +7,7 @@ export interface LinterConfig { } export class Linter { - private worker: Worker; - private widgets: any[] = []; + private worker!: Worker; private errorId = 0; constructor(private config: LinterConfig) {} diff --git a/docs/src/app-linter/plain-reporter.ts b/docs/src/app-linter/plain-reporter.ts index dbe2955ac..1f291718b 100644 --- a/docs/src/app-linter/plain-reporter.ts +++ b/docs/src/app-linter/plain-reporter.ts @@ -21,10 +21,10 @@ export class PlainReporter implements Reporter { } highlight(id: any) { - document.getElementById(id).classList.add('error-highlight'); + document.getElementById(id)!.classList.add('error-highlight'); } dropHighlight(id: any) { - document.getElementById(id).classList.remove('error-highlight'); + document.getElementById(id)!.classList.remove('error-highlight'); } } diff --git a/docs/src/app.ts b/docs/src/app.ts index 1d1b7ec8a..600d9bd1a 100644 --- a/docs/src/app.ts +++ b/docs/src/app.ts @@ -57,7 +57,7 @@ const editor = new ErrorReportingEditor( theme: 'material', lineNumbers: true }) as Editor, - new PlainReporter(new HtmlFormatter(), document.getElementById('warnings-header'), document.getElementById('warnings')) + new PlainReporter(new HtmlFormatter(), document.getElementById('warnings-header')!, document.getElementById('warnings')!) ); let unlocked = true; diff --git a/docs/src/worker/web-linter.ts b/docs/src/worker/web-linter.ts index c9cca5da9..55dd76c75 100644 --- a/docs/src/worker/web-linter.ts +++ b/docs/src/worker/web-linter.ts @@ -3,7 +3,7 @@ import * as ts from 'typescript'; import * as Linter from 'tslint'; import { LintResult } from 'tslint'; -export function getSourceFile(fileName: string, source: string): ts.SourceFile { +export function getSourceFile(fileName: string, source: string): ts.SourceFile | undefined { const normalizedName = fileName; const compilerOptions = createCompilerOptions(); @@ -15,14 +15,11 @@ export function getSourceFile(fileName: string, source: string): ts.SourceFile { getDirectories: (_path: string) => [], getNewLine: () => '\n', getSourceFile: (filenameToGet: string) => { - if (filenameToGet === normalizedName) { - return ts.createSourceFile(filenameToGet, source, compilerOptions.target, true); - } - return undefined; + return filenameToGet === normalizedName ? ts.createSourceFile(filenameToGet, source, compilerOptions.target!, true) : undefined; }, - readFile: () => null, + readFile: () => undefined, useCaseSensitiveFileNames: () => true, - writeFile: () => null + writeFile: () => undefined }; const program = ts.createProgram([normalizedName], compilerOptions, compilerHost); @@ -40,7 +37,7 @@ export class WebLinter { private failures: Linter.RuleFailure[] = []; public lint(fileName: string, source: string, enabledRules: any): void { - let sourceFile: ts.SourceFile = getSourceFile(fileName, source); + let sourceFile = getSourceFile(fileName, source); if (sourceFile === undefined) { throw new Error(`Invalid source file: ${fileName}. Ensure that the files supplied to lint have a .ts or .tsx extension.`); diff --git a/src/angular/config.ts b/src/angular/config.ts index 3487eff75..a00b4d24e 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -1,42 +1,36 @@ -import * as ts from 'typescript'; import { CodeWithSourceMap } from './metadata'; -export interface UrlResolver { - (url: string, d: ts.Decorator): string; +export interface StyleTransformer { + (style: string, url?: string): CodeWithSourceMap; } export interface TemplateTransformer { - (template: string, url: string, d: ts.Decorator): CodeWithSourceMap; + (template: string, url?: string): CodeWithSourceMap; } -export interface StyleTransformer { - (style: string, url: string, d: ts.Decorator): CodeWithSourceMap; +export interface UrlResolver { + (url: string | null): string | null; } -export const LogLevel = { - None: 0, - Error: 0b001, - Info: 0b011, - Debug: 0b111 -}; +export const LogLevel = { Debug: 0b111, Error: 0b001, Info: 0b011, None: 0 }; export interface Config { interpolation: [string, string]; + logLevel: number; + predefinedDirectives: DirectiveDeclaration[]; resolveUrl: UrlResolver; - transformTemplate: TemplateTransformer; transformStyle: StyleTransformer; - predefinedDirectives: DirectiveDeclaration[]; - logLevel: number; + transformTemplate: TemplateTransformer; } export interface DirectiveDeclaration { - selector: string; exportAs?: string; - inputs?: string[]; - outputs?: string[]; - hostProperties?: string[]; hostAttributes?: string[]; hostListeners?: string[]; + hostProperties?: string[]; + inputs?: string[]; + outputs?: string[]; + selector: string; } let BUILD_TYPE = '<%= BUILD_TYPE %>'; @@ -44,23 +38,7 @@ let BUILD_TYPE = '<%= BUILD_TYPE %>'; export const Config: Config = { interpolation: ['{{', '}}'], - resolveUrl(url: string, d: ts.Decorator) { - return url; - }, - - transformTemplate(code: string, url: string, d: ts.Decorator) { - if (!url || url.endsWith('.html')) { - return { code, url }; - } - return { code: '', url }; - }, - - transformStyle(code: string, url: string, d: ts.Decorator) { - if (!url || url.endsWith('.css')) { - return { code, url }; - } - return { code: '', url }; - }, + logLevel: BUILD_TYPE === 'dev' ? LogLevel.Debug : LogLevel.None, predefinedDirectives: [ { selector: 'form:not([ngNoForm]):not([formGroup]), ngForm, [ngForm]', exportAs: 'ngForm' }, @@ -89,7 +67,25 @@ export const Config: Config = { { selector: 'md-select', exportAs: 'mdSelect' } ], - logLevel: BUILD_TYPE === 'dev' ? LogLevel.Debug : LogLevel.None + resolveUrl(url: string | null) { + return url; + }, + + transformStyle(code: string, url?: string) { + if (!url || url.endsWith('.css')) { + return { code, url }; + } + + return { code: '', url }; + }, + + transformTemplate(code: string, url?: string) { + if (!url || url.endsWith('.html')) { + return { code, url }; + } + + return { code: '', url }; + } }; try { diff --git a/src/angular/metadata.ts b/src/angular/metadata.ts index 687be2c75..54043bd7b 100644 --- a/src/angular/metadata.ts +++ b/src/angular/metadata.ts @@ -3,35 +3,30 @@ import { RawSourceMap } from 'source-map'; export interface CodeWithSourceMap { code: string; - source?: string; map?: RawSourceMap; + source?: string; } -export interface TemplateMetadata { - template: CodeWithSourceMap; - node: ts.Node; - url: string; +interface PropertyMetadata { + node?: ts.Node; + url?: string; } -export interface StyleMetadata { +export interface StyleMetadata extends PropertyMetadata { style: CodeWithSourceMap; - node: ts.Node; - url: string; } -export interface StylesMetadata { - [index: number]: StyleMetadata; - length: number; - push(e: StyleMetadata): number; +export interface TemplateMetadata extends PropertyMetadata { + template: CodeWithSourceMap; } export class DirectiveMetadata { - selector: string; - controller: ts.ClassDeclaration; - decorator: ts.Decorator; + controller!: ts.ClassDeclaration; + decorator!: ts.Decorator; + selector!: string; } export class ComponentMetadata extends DirectiveMetadata { - template: TemplateMetadata; - styles: StylesMetadata; + styles!: StyleMetadata[]; + template!: TemplateMetadata; } diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index dde8517c9..a6a298acd 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -1,25 +1,22 @@ import * as ts from 'typescript'; -import { FileResolver } from './fileResolver/fileResolver'; -import { AbstractResolver, MetadataUrls } from './urlResolvers/abstractResolver'; -import { UrlResolver } from './urlResolvers/urlResolver'; -import { PathResolver } from './urlResolvers/pathResolver'; - -import { logger } from '../util/logger'; - -import { Config } from './config'; - -import { DirectiveMetadata, ComponentMetadata, CodeWithSourceMap, TemplateMetadata, StylesMetadata, StyleMetadata } from './metadata'; -import { Maybe, unwrapFirst, ifTrue, listToMaybe } from '../util/function'; import { callExpression, - withIdentifier, + decoratorArgument, + getStringInitializerFromProperty, hasProperties, isSimpleTemplateString, - getStringInitializerFromProperty, - decoratorArgument + withIdentifier } from '../util/astQuery'; -import { getTemplate, getInlineStyle } from '../util/ngQuery'; +import { ifTrue, listToMaybe, Maybe, unwrapFirst } from '../util/function'; +import { logger } from '../util/logger'; +import { getInlineStyle, getTemplate } from '../util/ngQuery'; import { maybeNodeArray } from '../util/utils'; +import { Config } from './config'; +import { FileResolver } from './fileResolver/fileResolver'; +import { CodeWithSourceMap, ComponentMetadata, DirectiveMetadata, StyleMetadata, TemplateMetadata } from './metadata'; +import { AbstractResolver, MetadataUrls } from './urlResolvers/abstractResolver'; +import { PathResolver } from './urlResolvers/pathResolver'; +import { UrlResolver } from './urlResolvers/urlResolver'; const normalizeTransformed = (t: CodeWithSourceMap) => { if (!t.map) { @@ -36,21 +33,21 @@ export class MetadataReader { this._urlResolver = this._urlResolver || new UrlResolver(new PathResolver()); } - read(d: ts.ClassDeclaration): DirectiveMetadata { - const componentMetadata = unwrapFirst( - maybeNodeArray(d.decorators).map((dec: ts.Decorator) => { + read(d: ts.ClassDeclaration): DirectiveMetadata | undefined { + const componentMetadata = unwrapFirst( + maybeNodeArray(d.decorators!).map(dec => { return Maybe.lift(dec) .bind(callExpression) - .bind(withIdentifier('Component')) + .bind(withIdentifier('Component') as any) .fmap(() => this.readComponentMetadata(d, dec)); }) ); - const directiveMetadata = unwrapFirst( - maybeNodeArray(d.decorators).map((dec: ts.Decorator) => + const directiveMetadata = unwrapFirst( + maybeNodeArray(d.decorators!).map(dec => Maybe.lift(dec) .bind(callExpression) - .bind(withIdentifier('Directive')) + .bind(withIdentifier('Directive') as any) .fmap(() => this.readDirectiveMetadata(d, dec)) ) ); @@ -60,8 +57,8 @@ export class MetadataReader { protected readDirectiveMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata { const selector = this.getDecoratorArgument(dec) - .bind(expr => getStringInitializerFromProperty('selector', expr.properties)) - .fmap(initializer => initializer.text); + .bind(expr => getStringInitializerFromProperty('selector', expr!.properties)) + .fmap(initializer => initializer!.text); return Object.assign(new DirectiveMetadata(), { controller: d, @@ -73,57 +70,48 @@ export class MetadataReader { protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): ComponentMetadata { const expr = this.getDecoratorArgument(dec); const directiveMetadata = this.readDirectiveMetadata(d, dec); - - const external_M = expr.fmap(() => this._urlResolver.resolve(dec)); - - const template_M: Maybe = external_M.bind(external => this.readComponentTemplateMetadata(dec, external)); - const style_M: Maybe = external_M.bind(external => this.readComponentStylesMetadata(dec, external)); + const external_M = expr.fmap(() => this._urlResolver!.resolve(dec)); + const style_M = external_M.bind(external => this.readComponentStylesMetadata(dec, external!)); + const template_M = external_M.bind(external => this.readComponentTemplateMetadata(dec, external!)); return Object.assign(new ComponentMetadata(), directiveMetadata, { - template: template_M.unwrap(), - styles: style_M.unwrap() + styles: style_M.unwrap(), + template: template_M.unwrap() }); } - protected getDecoratorArgument(decorator: ts.Decorator): Maybe { + protected getDecoratorArgument(decorator: ts.Decorator): Maybe { return decoratorArgument(decorator).bind(ifTrue(hasProperties)); } - protected readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe { + protected readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe { // Resolve Inline template return getTemplate(dec) - .fmap(inlineTemplate => ({ - template: normalizeTransformed(Config.transformTemplate(inlineTemplate.text, null, dec)), - url: null, - node: inlineTemplate + .fmap(inlineTemplate => ({ + node: inlineTemplate, + template: normalizeTransformed(Config.transformTemplate(inlineTemplate!.text)), + url: undefined })) .catch(() => // If there's no valid inline template, we resolve external template Maybe.lift(external.templateUrl).bind(url => - this._resolve(url).fmap(template => ({ - template: normalizeTransformed(Config.transformTemplate(template, url, dec)), - url, - node: null + this._resolve(url!).fmap(template => ({ + node: undefined, + template: normalizeTransformed(Config.transformTemplate(template!, url)), + url })) ) ); } - protected readComponentStylesMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe { + protected readComponentStylesMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe<(StyleMetadata | undefined)[] | undefined> { return getInlineStyle(dec) .fmap(inlineStyles => // Resolve Inline styles - inlineStyles.elements - .map((inlineStyle: ts.Expression) => { - if (isSimpleTemplateString(inlineStyle)) { - return { - style: normalizeTransformed(Config.transformStyle(inlineStyle.text, null, dec)), - url: null, - node: inlineStyle as ts.Node - }; - } - }) - .filter(v => !!v) + inlineStyles!.elements.filter(inlineStyle => isSimpleTemplateString(inlineStyle)).map(inlineStyle => ({ + node: inlineStyle as ts.Node, + style: normalizeTransformed(Config.transformStyle((inlineStyle as ts.StringLiteral | ts.NoSubstitutionTemplateLiteral).text)) + })) ) .catch(() => // If there's no valid inline styles, we resolve external styles @@ -132,19 +120,19 @@ export class MetadataReader { urls.map(( url // Resolve each style URL and transform to metadata ) => - this._resolve(url).fmap(style => ({ - style: normalizeTransformed(Config.transformStyle(style, url, dec)), - url, - node: null + this._resolve(url).fmap(style => ({ + node: undefined, + style: normalizeTransformed(Config.transformStyle(style!)), + url })) ) ) // merge Maybe[] to Maybe - .bind(url_Ms => listToMaybe(url_Ms)) + .bind(url => listToMaybe(url as any) as any) ); } - private _resolve(url: string): Maybe { + private _resolve(url: string): Maybe { try { return Maybe.lift(this._fileResolver.resolve(url)); } catch (e) { diff --git a/src/angular/ngWalker.ts b/src/angular/ngWalker.ts index aa6d9769a..d71978129 100644 --- a/src/angular/ngWalker.ts +++ b/src/angular/ngWalker.ts @@ -1,35 +1,31 @@ +import * as compiler from '@angular/compiler'; import * as Lint from 'tslint'; import * as ts from 'typescript'; -import * as compiler from '@angular/compiler'; -import { parseTemplate } from './templates/templateParser'; - -import { parseCss } from './styles/parseCss'; -import { CssAst } from './styles/cssAst'; +import { logger } from '../util/logger'; +import { getDecoratorName, maybeNodeArray } from '../util/utils'; +import { Config } from './config'; +import { ComponentMetadata, DirectiveMetadata, StyleMetadata } from './metadata'; +import { MetadataReader } from './metadataReader'; +import { ngWalkerFactoryUtils } from './ngWalkerFactoryUtils'; import { BasicCssAstVisitor, CssAstVisitorCtrl } from './styles/basicCssAstVisitor'; - -import { RecursiveAngularExpressionVisitorCtr, BasicTemplateAstVisitor, TemplateAstVisitorCtr } from './templates/basicTemplateAstVisitor'; +import { CssAst } from './styles/cssAst'; +import { parseCss } from './styles/parseCss'; +import { BasicTemplateAstVisitor, RecursiveAngularExpressionVisitorCtr, TemplateAstVisitorCtr } from './templates/basicTemplateAstVisitor'; import { RecursiveAngularExpressionVisitor } from './templates/recursiveAngularExpressionVisitor'; import { ReferenceCollectorVisitor } from './templates/referenceCollectorVisitor'; +import { parseTemplate } from './templates/templateParser'; -import { MetadataReader } from './metadataReader'; -import { ComponentMetadata, DirectiveMetadata, StyleMetadata } from './metadata'; -import { ngWalkerFactoryUtils } from './ngWalkerFactoryUtils'; - -import { Config } from './config'; +const getDecoratorStringArgs = (decorator: ts.Decorator): (string | null)[] => { + const { expression } = decorator; + const args = ts.isCallExpression(expression) ? expression.arguments : ts.createNodeArray(); -import { logger } from '../util/logger'; -import { getDecoratorName, maybeNodeArray } from '../util/utils'; - -const getDecoratorStringArgs = (decorator: ts.Decorator) => { - let baseExpr = decorator.expression || {}; - let args = baseExpr.arguments || []; - return args.map(a => (a.kind === ts.SyntaxKind.StringLiteral ? a.text : null)); + return args.map(a => (ts.isStringLiteral(a) ? a.text : null)); }; export interface NgWalkerConfig { + cssVisitorCtrl?: CssAstVisitorCtrl; expressionVisitorCtrl?: RecursiveAngularExpressionVisitorCtr; templateVisitorCtrl?: TemplateAstVisitorCtr; - cssVisitorCtrl?: CssAstVisitorCtrl; } export class NgWalker extends Lint.RuleWalker { @@ -44,16 +40,16 @@ export class NgWalker extends Lint.RuleWalker { this._metadataReader = this._metadataReader || ngWalkerFactoryUtils.defaultMetadataReader(); this._config = Object.assign( { + cssVisitorCtrl: BasicCssAstVisitor, templateVisitorCtrl: BasicTemplateAstVisitor, - expressionVisitorCtrl: RecursiveAngularExpressionVisitor, - cssVisitorCtrl: BasicCssAstVisitor + expressionVisitorCtrl: RecursiveAngularExpressionVisitor }, this._config || {} ); } visitClassDeclaration(declaration: ts.ClassDeclaration) { - const metadata = this._metadataReader.read(declaration); + const metadata = this._metadataReader!.read(declaration); if (metadata instanceof ComponentMetadata) { this.visitNgComponent(metadata); } else if (metadata instanceof DirectiveMetadata) { @@ -134,7 +130,7 @@ export class NgWalker extends Lint.RuleWalker { protected visitNgComponent(metadata: ComponentMetadata) { const template = metadata.template; - const getPosition = (node: any) => { + const getPosition = (node: ts.Node) => { let pos = 0; if (node) { pos = node.pos + 1; @@ -144,25 +140,25 @@ export class NgWalker extends Lint.RuleWalker { } return pos; }; + + const { styles = [] } = metadata; + + for (const style of styles) { + try { + this.visitNgStyleHelper(parseCss(style.style.code), metadata, style, getPosition(style.node!)); + } catch (e) { + logger.error('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text, e); + } + } + if (template && template.template) { try { const templateAst = parseTemplate(template.template.code, Config.predefinedDirectives); - this.visitNgTemplateHelper(templateAst, metadata, getPosition(template.node)); + this.visitNgTemplateHelper(templateAst, metadata, getPosition(template.node!)); } catch (e) { logger.error('Cannot parse the template of', ((metadata.controller || {}).name || {}).text, e); } } - const styles = metadata.styles; - if (styles && styles.length) { - for (let i = 0; i < styles.length; i += 1) { - const style = styles[i]; - try { - this.visitNgStyleHelper(parseCss(style.style.code), metadata, style, getPosition(style.node)); - } catch (e) { - logger.error('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text, e); - } - } - } } protected visitNgModule(decorator: ts.Decorator) {} @@ -173,72 +169,74 @@ export class NgWalker extends Lint.RuleWalker { protected visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) {} - protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} + protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} - protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) {} + protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: (string | null)[]) {} - protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: string[]) {} + protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: (string | null)[]) {} - protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: string[]) {} + protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: (string | null)[]) {} - protected visitNgContentChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} + protected visitNgContentChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} - protected visitNgContentChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} + protected visitNgContentChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} - protected visitNgViewChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} + protected visitNgViewChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} - protected visitNgViewChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} + protected visitNgViewChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} protected visitNgTemplateHelper(roots: compiler.TemplateAst[], context: ComponentMetadata, baseStart: number) { if (!roots || !roots.length) { return; - } else { - const sourceFile = this.getContextSourceFile(context.template.url, context.template.template.source); - const referenceVisitor = new ReferenceCollectorVisitor(); - const visitor = new this._config.templateVisitorCtrl( - sourceFile, - this._originalOptions, - context, - baseStart, - this._config.expressionVisitorCtrl - ); - compiler.templateVisitAll(referenceVisitor, roots, null); - visitor._variables = referenceVisitor.variables; - roots.forEach(r => visitor.visit(r, context.controller)); - visitor - .getFailures() - .forEach(f => - this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) - ); } + + const sourceFile = this.getContextSourceFile(context.template.url!, context.template.template.source!); + const referenceVisitor = new ReferenceCollectorVisitor(); + const visitor = new this._config!.templateVisitorCtrl!( + sourceFile, + this._originalOptions, + context, + baseStart, + this._config!.expressionVisitorCtrl! + ); + compiler.templateVisitAll(referenceVisitor, roots, null); + visitor._variables = referenceVisitor.variables; + roots.forEach(r => visitor.visit(r, context.controller)); + visitor + .getFailures() + .forEach(f => + this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) + ); } protected visitNgStyleHelper(style: CssAst, context: ComponentMetadata, styleMetadata: StyleMetadata, baseStart: number) { if (!style) { return; - } else { - const sourceFile = this.getContextSourceFile(styleMetadata.url, styleMetadata.style.source); - const visitor = new this._config.cssVisitorCtrl(sourceFile, this._originalOptions, context, styleMetadata, baseStart); - style.visit(visitor); - visitor - .getFailures() - .forEach(f => - this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) - ); } + + const sourceFile = this.getContextSourceFile(styleMetadata.url!, styleMetadata.style.source!); + const visitor = new this._config!.cssVisitorCtrl!(sourceFile, this._originalOptions, context, styleMetadata, baseStart); + style.visit(visitor); + visitor + .getFailures() + .forEach(f => + this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) + ); } protected getContextSourceFile(path: string, content: string) { - const current = this.getSourceFile(); if (!path) { - return current; + return this.getSourceFile(); } + const sf = ts.createSourceFile(path, `\`${content}\``, ts.ScriptTarget.ES5); const original = sf.getFullText; + sf.getFullText = () => { const text = original.apply(sf); return text.substring(1, text.length - 1); }; + return sf; } } diff --git a/src/angular/sourceMappingVisitor.ts b/src/angular/sourceMappingVisitor.ts index caef1918d..05f652c42 100644 --- a/src/angular/sourceMappingVisitor.ts +++ b/src/angular/sourceMappingVisitor.ts @@ -119,7 +119,7 @@ export class SourceMappingVisitor extends RuleWalker { let absPos = getLineAndCharacterOfPosition(this.codeWithMap.code, pos); const result = this.consumer.originalPositionFor({ line: absPos.line + 1, column: absPos.character + 1 }); absPos = { line: result.line - 1, character: result.column - 1 }; - pos = getPositionOfLineAndCharacter(this.codeWithMap.source, absPos.line, absPos.character); + pos = getPositionOfLineAndCharacter(this.codeWithMap.source!, absPos.line, absPos.character); } catch (e) { console.log(e); } diff --git a/src/angular/styles/cssAst.ts b/src/angular/styles/cssAst.ts index d85637dc2..7c35532be 100644 --- a/src/angular/styles/cssAst.ts +++ b/src/angular/styles/cssAst.ts @@ -258,7 +258,7 @@ export class CssUnknownTokenListAst extends CssRuleAst { } } -export function mergeTokens(tokens: CssToken[], separator: string = ''): CssToken { +export function mergeTokens(tokens: CssToken[], separator = ''): CssToken { const mainToken = tokens[0]; let str = mainToken.strValue; for (let i = 1; i < tokens.length; i++) { diff --git a/src/angular/styles/cssLexer.ts b/src/angular/styles/cssLexer.ts index 3905f2cc8..20f501c8f 100644 --- a/src/angular/styles/cssLexer.ts +++ b/src/angular/styles/cssLexer.ts @@ -81,7 +81,7 @@ export class CssToken { } export class CssLexer { - scan(text: string, trackComments: boolean = false): CssScanner { + scan(text: string, trackComments = false): CssScanner { return new CssScanner(text, trackComments); } } @@ -118,19 +118,19 @@ function _trackWhitespace(mode: CssLexerMode) { } export class CssScanner { - peek: number; + peek!: number; peekPeek: number; - length: number = 0; - index: number = -1; - column: number = -1; - line: number = 0; + length = 0; + index = -1; + column = -1; + line = 0; /** @internal */ _currentMode: CssLexerMode = CssLexerMode.BLOCK; /** @internal */ _currentError: Error | null = null; - constructor(public input: string, private _trackComments: boolean = false) { + constructor(public input: string, private _trackComments = false) { this.length = this.input.length; this.peekPeek = this.peekAt(0); this.advance(); @@ -218,7 +218,7 @@ export class CssScanner { next = new CssToken(this.index, this.column, this.line, CssTokenType.EOF, 'end of file'); } - let isMatchingType: boolean = false; + let isMatchingType = false; if (type == CssTokenType.IdentifierOrNumber) { // TODO (matsko): implement array traversal for lookup here isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier; @@ -476,10 +476,8 @@ export class CssScanner { return false; } - error(message: string, errorTokenValue: string | null = null, doNotAdvance: boolean = false): CssToken { - const index: number = this.index; - const column: number = this.column; - const line: number = this.line; + error(message: string, errorTokenValue: string | null = null, doNotAdvance = false): CssToken { + const { column, index, line } = this; errorTokenValue = errorTokenValue || String.fromCharCode(this.peek); const invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue); const errorMessage = generateErrorMessage(this.input, message, errorTokenValue, index, line, column); diff --git a/src/angular/styles/cssParser.ts b/src/angular/styles/cssParser.ts index a0146a475..0b0b05dcc 100644 --- a/src/angular/styles/cssParser.ts +++ b/src/angular/styles/cssParser.ts @@ -106,9 +106,9 @@ export class ParsedCssResult { export class CssParser { private _errors: CssParseError[] = []; - private _file: ParseSourceFile; - private _scanner: CssScanner; - private _lastToken: CssToken; + private _file!: ParseSourceFile | null; + private _scanner!: CssScanner | null; + private _lastToken!: CssToken; /** * @param css the CSS code that will be parsed @@ -116,7 +116,7 @@ export class CssParser { */ parse(css: string, url: string): ParsedCssResult { const lexer = new CssLexer(); - this._file = new ParseSourceFile(css, url); + this._file! = new ParseSourceFile(css, url); this._scanner = lexer.scan(css, false); const ast = this._parseStyleSheet(EOF_DELIM_FLAG); @@ -133,9 +133,9 @@ export class CssParser { /** @internal */ _parseStyleSheet(delimiters: number): CssStyleSheetAst { const results: CssRuleAst[] = []; - this._scanner.consumeEmptyStatements(); - while (this._scanner.peek != chars.$EOF) { - this._scanner.setMode(CssLexerMode.BLOCK); + this._scanner!.consumeEmptyStatements(); + while (this._scanner!.peek != chars.$EOF) { + this._scanner!.setMode(CssLexerMode.BLOCK); results.push(this._parseRule(delimiters)); } let span: ParseSourceSpan | null = null; @@ -150,7 +150,7 @@ export class CssParser { /** @internal */ _getSourceContent(): string { - return this._scanner != null ? this._scanner.input : ''; + return this._scanner != null ? this._scanner!.input : ''; } /** @internal */ @@ -170,16 +170,16 @@ export class CssParser { // occur, any other errors associated with this will be collected token = this._lastToken; } - startLoc = new ParseLocation(this._file, token.index, token.line, token.column); + startLoc = new ParseLocation(this._file!, token.index, token.line, token.column); } if (end == null) { end = this._lastToken; } - let endLine: number = -1; - let endColumn: number = -1; - let endIndex: number = -1; + let endLine = -1; + let endColumn = -1; + let endIndex = -1; if (end instanceof CssAst) { endLine = end.location.end.line!; endColumn = end.location.end.col!; @@ -190,7 +190,7 @@ export class CssParser { endIndex = end.index; } - const endLoc = new ParseLocation(this._file, endIndex, endLine, endColumn); + const endLoc = new ParseLocation(this._file!, endIndex, endLine, endColumn); return new ParseSourceSpan(startLoc, endLoc); } @@ -237,7 +237,7 @@ export class CssParser { /** @internal */ _parseRule(delimiters: number): CssRuleAst { - if (this._scanner.peek == chars.$AT) { + if (this._scanner!.peek == chars.$AT) { return this._parseAtRule(delimiters); } return this._parseSelectorRule(delimiters); @@ -247,7 +247,7 @@ export class CssParser { _parseAtRule(delimiters: number): CssRuleAst { const start = this._getScannerIndex(); - this._scanner.setMode(CssLexerMode.BLOCK); + this._scanner!.setMode(CssLexerMode.BLOCK); const token = this._scan(); const startToken = token; @@ -266,8 +266,8 @@ export class CssParser { case BlockType.Namespace: case BlockType.Import: let value = this._parseValue(delimiters); - this._scanner.setMode(CssLexerMode.BLOCK); - this._scanner.consumeEmptyStatements(); + this._scanner!.setMode(CssLexerMode.BLOCK); + this._scanner!.consumeEmptyStatements(); span = this._generateSourceSpan(startToken, value); return new CssInlineRuleAst(span, type, value); @@ -286,7 +286,7 @@ export class CssParser { return new CssKeyframeRuleAst(span, name, block); case BlockType.MediaQuery: - this._scanner.setMode(CssLexerMode.MEDIA_QUERY); + this._scanner!.setMode(CssLexerMode.MEDIA_QUERY); tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG); endToken = tokens[tokens.length - 1]; // we do not track the whitespace after the mediaQuery predicate ends @@ -303,7 +303,7 @@ export class CssParser { case BlockType.Document: case BlockType.Supports: case BlockType.Page: - this._scanner.setMode(CssLexerMode.AT_RULE_QUERY); + this._scanner!.setMode(CssLexerMode.AT_RULE_QUERY); tokens = this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG); endToken = tokens[tokens.length - 1]; // we do not track the whitespace after this block rule predicate ends @@ -321,7 +321,7 @@ export class CssParser { default: let listOfTokens: CssToken[] = []; let tokenName = token.strValue; - this._scanner.setMode(CssLexerMode.ALL); + this._scanner!.setMode(CssLexerMode.ALL); this._error( generateErrorMessage( this._getSourceContent(), @@ -337,7 +337,7 @@ export class CssParser { this._collectUntilDelim(delimiters | LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG).forEach(token => { listOfTokens.push(token); }); - if (this._scanner.peek == chars.$LBRACE) { + if (this._scanner!.peek == chars.$LBRACE) { listOfTokens.push(this._consume(CssTokenType.Character, '{')); this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG).forEach(token => { listOfTokens.push(token); @@ -375,8 +375,8 @@ export class CssParser { span = this._generateSourceSpan(startSelector, endToken); ruleAst = new CssUnknownTokenListAst(span, name, innerTokens); } - this._scanner.setMode(CssLexerMode.BLOCK); - this._scanner.consumeEmptyStatements(); + this._scanner!.setMode(CssLexerMode.BLOCK); + this._scanner!.consumeEmptyStatements(); return ruleAst; } @@ -389,13 +389,13 @@ export class CssParser { while (isParsingSelectors) { selectors.push(this._parseSelector(delimiters)); - isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters); + isParsingSelectors = !characterContainsDelimiter(this._scanner!.peek, delimiters); if (isParsingSelectors) { this._consume(CssTokenType.Character, ','); - isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters); + isParsingSelectors = !characterContainsDelimiter(this._scanner!.peek, delimiters); if (isParsingSelectors) { - this._scanner.consumeWhitespace(); + this._scanner!.consumeWhitespace(); } } } @@ -405,7 +405,7 @@ export class CssParser { /** @internal */ _scan(): CssToken { - const output = this._scanner.scan()!; + const output = this._scanner!.scan()!; const token = output.token; const error = output.error; if (error != null) { @@ -417,12 +417,12 @@ export class CssParser { /** @internal */ _getScannerIndex(): number { - return this._scanner.index; + return this._scanner!.index; } /** @internal */ _consume(type: CssTokenType, value: string | null = null): CssToken { - const output = this._scanner.consume(type, value); + const output = this._scanner!.consume(type, value); const token = output.token; const error = output.error; if (error != null) { @@ -435,12 +435,12 @@ export class CssParser { /** @internal */ _parseKeyframeBlock(delimiters: number): CssBlockAst { delimiters |= RBRACE_DELIM_FLAG; - this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK); + this._scanner!.setMode(CssLexerMode.KEYFRAME_BLOCK); const startToken = this._consume(CssTokenType.Character, '{'); const definitions: CssKeyframeDefinitionAst[] = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { definitions.push(this._parseKeyframeDefinition(delimiters)); } @@ -452,12 +452,11 @@ export class CssParser { /** @internal */ _parseKeyframeDefinition(delimiters: number): CssKeyframeDefinitionAst { - const start = this._getScannerIndex(); const stepTokens: CssToken[] = []; delimiters |= LBRACE_DELIM_FLAG; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { stepTokens.push(this._parseKeyframeLabel(delimiters | COMMA_DELIM_FLAG)); - if (this._scanner.peek != chars.$LBRACE) { + if (this._scanner!.peek != chars.$LBRACE) { this._consume(CssTokenType.Character, ','); } } @@ -465,13 +464,13 @@ export class CssParser { const span = this._generateSourceSpan(stepTokens[0], stylesBlock); const ast = new CssKeyframeDefinitionAst(span, stepTokens, stylesBlock!); - this._scanner.setMode(CssLexerMode.BLOCK); + this._scanner!.setMode(CssLexerMode.BLOCK); return ast; } /** @internal */ _parseKeyframeLabel(delimiters: number): CssToken { - this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK); + this._scanner!.setMode(CssLexerMode.KEYFRAME_BLOCK); return mergeTokens(this._collectUntilDelim(delimiters)); } @@ -487,14 +486,14 @@ export class CssParser { const startToken = this._consume(CssTokenType.Character, ':'); const tokens = [startToken]; - if (this._scanner.peek == chars.$COLON) { + if (this._scanner!.peek == chars.$COLON) { // ::something tokens.push(this._consume(CssTokenType.Character, ':')); } const innerSelectors: CssSelectorAst[] = []; - this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR); + this._scanner!.setMode(CssLexerMode.PSEUDO_SELECTOR); // host, host-context, lang, not, nth-child are all identifiers const pseudoSelectorToken = this._consume(CssTokenType.Identifier); @@ -502,8 +501,8 @@ export class CssParser { tokens.push(pseudoSelectorToken); // host(), lang(), nth-child(), etc... - if (this._scanner.peek == chars.$LPAREN) { - this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS); + if (this._scanner!.peek == chars.$LPAREN) { + this._scanner!.setMode(CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS); const openParenToken = this._consume(CssTokenType.Character, '('); tokens.push(openParenToken); @@ -526,7 +525,7 @@ export class CssParser { // this branch is for things like "en-us, 2k + 1, etc..." // which all end up in pseudoSelectors like :lang, :nth-child, etc.. const innerValueDelims = delimiters | LBRACE_DELIM_FLAG | COLON_DELIM_FLAG | RPAREN_DELIM_FLAG | LPAREN_DELIM_FLAG; - while (!characterContainsDelimiter(this._scanner.peek, innerValueDelims)) { + while (!characterContainsDelimiter(this._scanner!.peek, innerValueDelims)) { const token = this._scan(); tokens.push(token); } @@ -550,24 +549,24 @@ export class CssParser { delimiters |= COMMA_DELIM_FLAG; - this._scanner.setMode(CssLexerMode.SELECTOR); + this._scanner!.setMode(CssLexerMode.SELECTOR); const selectorCssTokens: CssToken[] = []; const pseudoSelectors: CssPseudoSelectorAst[] = []; let previousToken: CssToken = undefined!; const selectorPartDelimiters = delimiters | SPACE_DELIM_FLAG; - let loopOverSelector = !characterContainsDelimiter(this._scanner.peek, selectorPartDelimiters); + let loopOverSelector = !characterContainsDelimiter(this._scanner!.peek, selectorPartDelimiters); let hasAttributeError = false; while (loopOverSelector) { - const peek = this._scanner.peek; + const peek = this._scanner!.peek; switch (peek) { case chars.$COLON: let innerPseudo = this._parsePseudoSelector(delimiters); pseudoSelectors.push(innerPseudo); - this._scanner.setMode(CssLexerMode.SELECTOR); + this._scanner!.setMode(CssLexerMode.SELECTOR); break; case chars.$LBRACKET: @@ -575,16 +574,16 @@ export class CssParser { // allow attribute [] values. And this also will catch any errors // if an extra "[" is used inside. selectorCssTokens.push(this._scan()); - this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR); + this._scanner!.setMode(CssLexerMode.ATTRIBUTE_SELECTOR); break; case chars.$RBRACKET: - if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) { + if (this._scanner!.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) { hasAttributeError = true; } // we set the mode early because attribute mode does not // allow attribute [] values - this._scanner.setMode(CssLexerMode.SELECTOR); + this._scanner!.setMode(CssLexerMode.SELECTOR); selectorCssTokens.push(this._scan()); break; @@ -600,10 +599,10 @@ export class CssParser { break; } - loopOverSelector = !characterContainsDelimiter(this._scanner.peek, selectorPartDelimiters); + loopOverSelector = !characterContainsDelimiter(this._scanner!.peek, selectorPartDelimiters); } - hasAttributeError = hasAttributeError || this._scanner.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR; + hasAttributeError = hasAttributeError || this._scanner!.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR; if (hasAttributeError) { this._error(`Unbalanced CSS attribute selector at column ${previousToken.line}:${previousToken.column}`, previousToken); } @@ -615,11 +614,11 @@ export class CssParser { let operator: CssToken | null = null; let operatorScanCount = 0; let lastOperatorToken: CssToken | null = null; - if (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + if (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { while ( operator == null && - !characterContainsDelimiter(this._scanner.peek, delimiters) && - isSelectorOperatorCharacter(this._scanner.peek) + !characterContainsDelimiter(this._scanner!.peek, delimiters) && + isSelectorOperatorCharacter(this._scanner!.peek) ) { let token = this._scan(); const tokenOperator = token.strValue; @@ -654,7 +653,7 @@ export class CssParser { case GT_CHARACTER: // >>> operator - if (this._scanner.peek == chars.$GT && this._scanner.peekPeek == chars.$GT) { + if (this._scanner!.peek == chars.$GT && this._scanner!.peekPeek == chars.$GT) { this._consume(CssTokenType.Character, GT_CHARACTER); this._consume(CssTokenType.Character, GT_CHARACTER); token = new CssToken( @@ -680,14 +679,14 @@ export class CssParser { } } - this._scanner.consumeWhitespace(); + this._scanner!.consumeWhitespace(); const strValue = this._extractSourceContent(start, end); // if we do come across one or more spaces inside of // the operators loop then an empty space is still a // valid operator to use if something else was not found - if (operator == null && operatorScanCount > 0 && this._scanner.peek != chars.$LBRACE) { + if (operator == null && operatorScanCount > 0 && this._scanner!.peek != chars.$LBRACE) { operator = lastOperatorToken; } @@ -715,12 +714,12 @@ export class CssParser { /** @internal */ _parseSelector(delimiters: number): CssSelectorAst { delimiters |= COMMA_DELIM_FLAG; - this._scanner.setMode(CssLexerMode.SELECTOR); + this._scanner!.setMode(CssLexerMode.SELECTOR); const simpleSelectors: CssSimpleSelectorAst[] = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { simpleSelectors.push(this._parseSimpleSelector(delimiters)); - this._scanner.consumeWhitespace(); + this._scanner!.consumeWhitespace(); } const firstSelector = simpleSelectors[0]; @@ -733,33 +732,29 @@ export class CssParser { _parseValue(delimiters: number): CssStyleValueAst { delimiters |= RBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG | NEWLINE_DELIM_FLAG; - this._scanner.setMode(CssLexerMode.STYLE_VALUE); + this._scanner!.setMode(CssLexerMode.STYLE_VALUE); const start = this._getScannerIndex(); const tokens: CssToken[] = []; - let wsStr = ''; let previous: CssToken = undefined!; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { let token: CssToken; - if (previous != null && previous.type == CssTokenType.Identifier && this._scanner.peek == chars.$LPAREN) { + if (previous != null && previous.type == CssTokenType.Identifier && this._scanner!.peek == chars.$LPAREN) { token = this._consume(CssTokenType.Character, '('); tokens.push(token); - this._scanner.setMode(CssLexerMode.STYLE_VALUE_FUNCTION); + this._scanner!.setMode(CssLexerMode.STYLE_VALUE_FUNCTION); token = this._scan(); tokens.push(token); - this._scanner.setMode(CssLexerMode.STYLE_VALUE); + this._scanner!.setMode(CssLexerMode.STYLE_VALUE); token = this._consume(CssTokenType.Character, ')'); tokens.push(token); } else { token = this._scan(); - if (token.type == CssTokenType.Whitespace) { - wsStr += token.strValue; - } else { - wsStr = ''; + if (token.type != CssTokenType.Whitespace) { tokens.push(token); } } @@ -767,9 +762,9 @@ export class CssParser { } const end = this._getScannerIndex() - 1; - this._scanner.consumeWhitespace(); + this._scanner!.consumeWhitespace(); - const code = this._scanner.peek; + const code = this._scanner!.peek; if (code == chars.$SEMICOLON) { this._consume(CssTokenType.Character, ';'); } else if (code != chars.$RBRACE) { @@ -796,7 +791,7 @@ export class CssParser { /** @internal */ _collectUntilDelim(delimiters: number, assertType: CssTokenType | null = null): CssToken[] { const tokens: CssToken[] = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { const val = assertType != null ? this._consume(assertType) : this._scan(); tokens.push(val); } @@ -807,20 +802,20 @@ export class CssParser { _parseBlock(delimiters: number): CssBlockAst { delimiters |= RBRACE_DELIM_FLAG; - this._scanner.setMode(CssLexerMode.BLOCK); + this._scanner!.setMode(CssLexerMode.BLOCK); const startToken = this._consume(CssTokenType.Character, '{'); - this._scanner.consumeEmptyStatements(); + this._scanner!.consumeEmptyStatements(); const results: CssRuleAst[] = []; - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { results.push(this._parseRule(delimiters)); } const endToken = this._consume(CssTokenType.Character, '}'); - this._scanner.setMode(CssLexerMode.BLOCK); - this._scanner.consumeEmptyStatements(); + this._scanner!.setMode(CssLexerMode.BLOCK); + this._scanner!.consumeEmptyStatements(); const span = this._generateSourceSpan(startToken, endToken); return new CssBlockAst(span, results); @@ -830,7 +825,7 @@ export class CssParser { _parseStyleBlock(delimiters: number): CssStylesBlockAst | null { delimiters |= RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG; - this._scanner.setMode(CssLexerMode.STYLE_BLOCK); + this._scanner!.setMode(CssLexerMode.STYLE_BLOCK); const startToken = this._consume(CssTokenType.Character, '{'); if (startToken.numValue != chars.$LBRACE) { @@ -838,17 +833,17 @@ export class CssParser { } const definitions: CssDefinitionAst[] = []; - this._scanner.consumeEmptyStatements(); + this._scanner!.consumeEmptyStatements(); - while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { + while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { definitions.push(this._parseDefinition(delimiters)); - this._scanner.consumeEmptyStatements(); + this._scanner!.consumeEmptyStatements(); } const endToken = this._consume(CssTokenType.Character, '}'); - this._scanner.setMode(CssLexerMode.STYLE_BLOCK); - this._scanner.consumeEmptyStatements(); + this._scanner!.setMode(CssLexerMode.STYLE_BLOCK); + this._scanner!.consumeEmptyStatements(); const span = this._generateSourceSpan(startToken, endToken); return new CssStylesBlockAst(span, definitions); @@ -856,17 +851,17 @@ export class CssParser { /** @internal */ _parseDefinition(delimiters: number): CssDefinitionAst { - this._scanner.setMode(CssLexerMode.STYLE_BLOCK); + this._scanner!.setMode(CssLexerMode.STYLE_BLOCK); let prop = this._consume(CssTokenType.Identifier); - let parseValue: boolean = false; + let parseValue = false; let value: CssStyleValueAst | null = null; let endToken: CssToken | CssStyleValueAst = prop; // the colon value separates the prop from the style. // there are a few cases as to what could happen if it // is missing - switch (this._scanner.peek) { + switch (this._scanner!.peek) { case chars.$SEMICOLON: case chars.$RBRACE: case chars.$EOF: @@ -875,7 +870,7 @@ export class CssParser { default: let propStr = [prop.strValue]; - if (this._scanner.peek != chars.$COLON) { + if (this._scanner!.peek != chars.$COLON) { // this will throw the error const nextValue = this._consume(CssTokenType.Character, ':'); propStr.push(nextValue.strValue); @@ -891,7 +886,7 @@ export class CssParser { } // this means we've reached the end of the definition and/or block - if (this._scanner.peek == chars.$COLON) { + if (this._scanner!.peek == chars.$COLON) { this._consume(CssTokenType.Character, ':'); parseValue = true; } @@ -931,7 +926,7 @@ export class CssParser { /** @internal */ _error(message: string, problemToken: CssToken) { const length = problemToken.strValue.length; - const error = CssParseError.create(this._file, 0, problemToken.line, problemToken.column, length, message); + const error = CssParseError.create(this._file!, 0, problemToken.line, problemToken.column, length, message); this._errors.push(error); } } diff --git a/src/angular/templates/basicTemplateAstVisitor.ts b/src/angular/templates/basicTemplateAstVisitor.ts index af7a29d92..d847fa138 100644 --- a/src/angular/templates/basicTemplateAstVisitor.ts +++ b/src/angular/templates/basicTemplateAstVisitor.ts @@ -118,21 +118,21 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast visitNgContent(ast: ast.NgContentAst, context: any): any {} visitEmbeddedTemplate(ast: ast.EmbeddedTemplateAst, context: any): any { - ast.directives.forEach(d => this.visit(d, context)); - ast.variables.forEach(v => this.visit(v, context)); - ast.children.forEach(e => this.visit(e, context)); - ast.outputs.forEach(o => this.visit(o, context)); - ast.attrs.forEach(a => this.visit(a, context)); - ast.references.forEach(r => this.visit(r, context)); + ast.directives.forEach(d => this.visit!(d, context)); + ast.variables.forEach(v => this.visit!(v, context)); + ast.children.forEach(e => this.visit!(e, context)); + ast.outputs.forEach(o => this.visit!(o, context)); + ast.attrs.forEach(a => this.visit!(a, context)); + ast.references.forEach(r => this.visit!(r, context)); } visitElement(element: ast.ElementAst, context: any): any { - element.references.forEach(r => this.visit(r, context)); - element.inputs.forEach(i => this.visit(i, context)); - element.outputs.forEach(o => this.visit(o, context)); - element.attrs.forEach(a => this.visit(a, context)); - element.children.forEach(e => this.visit(e, context)); - element.directives.forEach(d => this.visit(d, context)); + element.references.forEach(r => this.visit!(r, context)); + element.inputs.forEach(i => this.visit!(i, context)); + element.outputs.forEach(o => this.visit!(o, context)); + element.attrs.forEach(a => this.visit!(a, context)); + element.children.forEach(e => this.visit!(e, context)); + element.directives.forEach(d => this.visit!(d, context)); } visitReference(ast: ast.ReferenceAst, context: any): any {} @@ -167,9 +167,9 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast visitText(text: ast.TextAst, context: any): any {} visitDirective(ast: ast.DirectiveAst, context: any): any { - ast.inputs.forEach(o => this.visit(o, context)); - ast.hostProperties.forEach(p => this.visit(p, context)); - ast.hostEvents.forEach(e => this.visit(e, context)); + ast.inputs.forEach(o => this.visit!(o, context)); + ast.hostProperties.forEach(p => this.visit!(p, context)); + ast.hostEvents.forEach(e => this.visit!(e, context)); } visitDirectiveProperty(prop: ast.BoundDirectivePropertyAst, context: any): any { diff --git a/src/angular/templates/recursiveAngularExpressionVisitor.ts b/src/angular/templates/recursiveAngularExpressionVisitor.ts index d9cdc36cc..44bb61c3c 100644 --- a/src/angular/templates/recursiveAngularExpressionVisitor.ts +++ b/src/angular/templates/recursiveAngularExpressionVisitor.ts @@ -50,7 +50,7 @@ export class RecursiveAngularExpressionVisitor extends SourceMappingVisitor impl } visitFunctionCall(ast: e.FunctionCall, context: any): any { - ast.target.visit(this, context); + ast.target!.visit(this, context); this.visitAll(ast.args, context); return null; } diff --git a/src/angular/templates/referenceCollectorVisitor.ts b/src/angular/templates/referenceCollectorVisitor.ts index df65adbca..b088f5c51 100644 --- a/src/angular/templates/referenceCollectorVisitor.ts +++ b/src/angular/templates/referenceCollectorVisitor.ts @@ -31,12 +31,12 @@ export class ReferenceCollectorVisitor implements ast.TemplateAstVisitor { visitEmbeddedTemplate(ast: ast.EmbeddedTemplateAst, context: any): any { ast.references.forEach(r => (this._variables[r.name] = true)); - ast.children.forEach(e => this.visit(e, context)); + ast.children.forEach(e => this.visit!(e, context)); } visitElement(element: ast.ElementAst, context: any): any { element.references.forEach(r => (this._variables[r.name] = true)); - element.children.forEach(e => this.visit(e, context)); + element.children.forEach(e => this.visit!(e, context)); } get variables() { diff --git a/src/angular/templates/templateParser.ts b/src/angular/templates/templateParser.ts index 049854baf..0b5731e21 100644 --- a/src/angular/templates/templateParser.ts +++ b/src/angular/templates/templateParser.ts @@ -13,9 +13,9 @@ const dummyMetadataFactory = (declaration: DirectiveDeclaration) => { return { inputs: declaration.inputs || [], outputs: declaration.outputs || [], - hostListeners: declaration.hostListeners || {}, - hostProperties: declaration.hostProperties || {}, - hostAttributes: declaration.hostAttributes || {}, + hostListeners: declaration.hostListeners || [], + hostProperties: declaration.hostProperties || [], + hostAttributes: declaration.hostAttributes || [], isSummary: true, type: { diDeps: [], @@ -45,7 +45,7 @@ class Console { warn(message: string) {} } -let defaultDirectives = []; +let defaultDirectives: DirectiveDeclaration[] = []; export const parseTemplate = (template: string, directives: DirectiveDeclaration[] = []) => { defaultDirectives = directives.map(d => dummyMetadataFactory(d)); @@ -91,7 +91,7 @@ export const parseTemplate = (template: string, directives: DirectiveDeclaration ); }); - const interpolation = Config.interpolation; + const { interpolation } = Config; // Make sure it works with 2.2.x & 2.3.x const summaryKind = ((compiler as any).CompileSummaryKind || {}).Template; @@ -131,7 +131,7 @@ export const parseTemplate = (template: string, directives: DirectiveDeclaration value: '', identifier: null }; - let result = null; + let result: any = null; try { SemVerDSL.lt('4.1.0', () => { result = tmplParser.tryParse( diff --git a/src/angular/urlResolvers/abstractResolver.ts b/src/angular/urlResolvers/abstractResolver.ts index a48a8bf9f..4ee5a5886 100644 --- a/src/angular/urlResolvers/abstractResolver.ts +++ b/src/angular/urlResolvers/abstractResolver.ts @@ -1,73 +1,64 @@ import * as ts from 'typescript'; -import { current } from '../../util/syntaxKind'; +import { isPropertyAssignment } from 'typescript/lib/typescript'; import { isSimpleTemplateString } from '../../util/utils'; -const kinds = current(); - export interface MetadataUrls { templateUrl: string; styleUrls: string[]; } export abstract class AbstractResolver { - abstract resolve(decorator: ts.Decorator): MetadataUrls; + abstract resolve(decorator: ts.Decorator): MetadataUrls | null; - protected getTemplateUrl(decorator: ts.Decorator): string { + protected getTemplateUrl(decorator: ts.Decorator): string | null { const arg = this.getDecoratorArgument(decorator); + if (!arg) { return null; } + const prop = arg.properties - .filter((p: ts.PropertyAssignment) => { - if ((p.name).text === 'templateUrl' && isSimpleTemplateString(p.initializer)) { - return true; - } - return false; - }) + .filter(p => (p.name as any).text === 'templateUrl' && isSimpleTemplateString((p as ts.PropertyAssignment).initializer)) .pop(); - if (prop) { - // We know that it's has an initializer because it's either - // a template string or a string literal. - return ((prop).initializer).text; - } else { - return null; - } + + // We know that it's has an initializer because it's either + // a template string or a string literal. + return prop ? ((prop as ts.PropertyAssignment).initializer as any).text : undefined; } protected getStyleUrls(decorator: ts.Decorator): string[] { const arg = this.getDecoratorArgument(decorator); + if (!arg) { return []; } + const prop = arg.properties - .filter((p: ts.PropertyAssignment) => { - if ((p.name).text === 'styleUrls' && p.initializer.kind === kinds.ArrayLiteralExpression) { - return true; - } - return false; - }) + .filter( + p => (p.name as any).text === 'styleUrls' && isPropertyAssignment(p) && p.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression + ) .pop(); + if (prop) { - return ((prop).initializer).elements - .filter((e: any) => { - return isSimpleTemplateString(e); - }) - .map((e: any) => { - return e.text; - }); - } else { - return []; + return ((prop as ts.PropertyAssignment).initializer as ts.ArrayLiteralExpression).elements + .filter(e => isSimpleTemplateString(e)) + .map(e => (e as any).text); } + + return []; } - protected getDecoratorArgument(decorator: ts.Decorator): ts.ObjectLiteralExpression { + protected getDecoratorArgument(decorator: ts.Decorator): ts.ObjectLiteralExpression | null { const expr = decorator.expression; + if (expr && expr.arguments && expr.arguments.length) { - const arg = expr.arguments[0]; - if (arg.kind === kinds.ObjectLiteralExpression && arg.properties) { + const arg = expr.arguments[0] as ts.ObjectLiteralExpression; + + if (arg.properties && arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { return arg; } } + return null; } } diff --git a/src/angular/urlResolvers/pathResolver.ts b/src/angular/urlResolvers/pathResolver.ts index 36d25d8a9..5fae181bb 100644 --- a/src/angular/urlResolvers/pathResolver.ts +++ b/src/angular/urlResolvers/pathResolver.ts @@ -1,7 +1,7 @@ import { join } from 'path'; export class PathResolver { - resolve(path: string, relative: string): string { + resolve(path: string, relative: string): string | null { if (typeof path !== 'string') { return null; } diff --git a/src/angular/urlResolvers/urlResolver.ts b/src/angular/urlResolvers/urlResolver.ts index 942c9c20b..163ac3545 100644 --- a/src/angular/urlResolvers/urlResolver.ts +++ b/src/angular/urlResolvers/urlResolver.ts @@ -3,11 +3,8 @@ import * as ts from 'typescript'; import { Config } from '../config'; import { AbstractResolver, MetadataUrls } from './abstractResolver'; import { dirname } from 'path'; -import { current } from '../../util/syntaxKind'; import { PathResolver } from './pathResolver'; -const kinds = current(); - export class UrlResolver extends AbstractResolver { constructor(private pathResolver: PathResolver) { super(); @@ -17,30 +14,35 @@ export class UrlResolver extends AbstractResolver { const templateUrl = this.getTemplateUrl(d); const styleUrls = this.getStyleUrls(d); const targetPath = this.getProgramFilePath(d); + if (targetPath) { const componentPath = dirname(targetPath); + return { - templateUrl: Config.resolveUrl(this.pathResolver.resolve(templateUrl, componentPath), d), + templateUrl: Config.resolveUrl(this.pathResolver.resolve(templateUrl!, componentPath))!, styleUrls: styleUrls.map((p: string) => { - return Config.resolveUrl(this.pathResolver.resolve(p, componentPath), d); + return Config.resolveUrl(this.pathResolver.resolve(p, componentPath))!; }) }; - } else { - return { - templateUrl: Config.resolveUrl(null, d), - styleUrls: [] - }; } + + return { + templateUrl: Config.resolveUrl(null)!, + styleUrls: [] + }; } private getProgramFilePath(d: any) { - let current: any = d; + let current = d; + while (current) { - if (current.kind === kinds.SourceFile) { + if (current.kind === ts.SyntaxKind.SourceFile) { return current.path || current.fileName; } + current = current.parent; } + return undefined; } } diff --git a/src/angularWhitespaceRule.ts b/src/angularWhitespaceRule.ts index f106f9a2f..47b7ad380 100644 --- a/src/angularWhitespaceRule.ts +++ b/src/angularWhitespaceRule.ts @@ -17,8 +17,7 @@ const stickyFlagUsable = (() => { } })(); -const InterpolationOpen = Config.interpolation[0]; -const InterpolationClose = Config.interpolation[1]; +const [InterpolationOpen, InterpolationClose] = Config.interpolation; const InterpolationWhitespaceRe = new RegExp(`${InterpolationOpen}(\\s*)(.*?)(\\s*)${InterpolationClose}`, 'g'); const SemicolonNoWhitespaceNotInSimpleQuoteRe = stickyFlagUsable ? new RegExp(`(?:[^';]|'[^']*'|;(?=\\s))+;(?=\\S)`, 'gy') @@ -91,7 +90,7 @@ class InterpolationWhitespaceVisitor extends BasicTemplateAstVisitor implements visitBoundText(text: ast.BoundTextAst, context: BasicTemplateAstVisitor): any { if (ExpTypes.ASTWithSource(text.value)) { // Note that will not be reliable for different interpolation symbols - const expr: any = (text.value).source; + const expr = (text.value as any).source; const checkWhiteSpace = ( subMatch: string, location: 'start' | 'end', @@ -144,7 +143,7 @@ class SemicolonTemplateVisitor extends BasicTemplateAstVisitor implements Config visitDirectiveProperty(prop: ast.BoundDirectivePropertyAst, context: BasicTemplateAstVisitor): any { if (prop.sourceSpan) { const directive = prop.sourceSpan.toString(); - const match = /^([^=]+=\s*)([^]*?)\s*$/.exec(directive); + const match = /^([^=]+=\s*)([^]*?)\s*$/.exec(directive)!; const rawExpression = match[2]; const positionFix = match[1].length + 1; const expr = rawExpression.slice(1, -1).trim(); @@ -205,7 +204,7 @@ class PipeWhitespaceVisitor extends RecursiveAngularExpressionVisitor implements sf = context.getSourceFile().getFullText(); exprText = sf.substring(exprStart, exprEnd); - const replacements = []; + const replacements: Lint.Fix = []; let parentheses = false; let leftBeginning: number; if (sf[exprEnd] === ')') { diff --git a/src/bananaInBoxRule.ts b/src/bananaInBoxRule.ts index 9456773c7..27b993e53 100644 --- a/src/bananaInBoxRule.ts +++ b/src/bananaInBoxRule.ts @@ -29,7 +29,8 @@ const getReplacements = (text: ast.BoundEventAst, absolutePosition: number) => { class BananaInBoxTemplateVisitor extends BasicTemplateAstVisitor { visitEvent(prop: ast.BoundEventAst, context: any): any { if (prop.name) { - let error = null; + let error: string | null = null; + if (InvalidSyntaxBoxRe.test(prop.name)) { error = 'Invalid binding syntax. Use [(expr)] instead'; } diff --git a/src/componentClassSuffixRule.ts b/src/componentClassSuffixRule.ts index 44515d121..a56634aed 100644 --- a/src/componentClassSuffixRule.ts +++ b/src/componentClassSuffixRule.ts @@ -2,8 +2,7 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; import { ComponentMetadata } from './angular/metadata'; -import { NgWalker } from './angular/ngWalker'; -import { F2, Maybe } from './util/function'; +import { Maybe } from './util/function'; import { Failure } from './walkerFactory/walkerFactory'; import { all, validateComponent } from './walkerFactory/walkerFn'; @@ -28,18 +27,20 @@ export class Rule extends Lint.Rules.AbstractRule { static readonly FAILURE_STRING = 'The name of the class %s should end with the suffix %s (https://angular.io/styleguide#style-02-03)'; - static walkerBuilder: F2 = all( - validateComponent((meta: ComponentMetadata, suffixList?: string[]) => + static walkerBuilder = all( + validateComponent((meta: ComponentMetadata, suffixList: string[] = []) => Maybe.lift(meta.controller) .fmap(controller => controller.name) .fmap(name => { - const className = name.text; - if (suffixList.length === 0) { - suffixList = ['Component']; - } - if (!Rule.validate(className, suffixList)) { - return [new Failure(name, sprintf(Rule.FAILURE_STRING, className, suffixList))]; + const { text } = name!; + const failures: Failure[] = []; + const suffixes = suffixList.length > 0 ? suffixList : ['Component']; + + if (!Rule.validate(text, suffixes)) { + failures.push(new Failure(name!, sprintf(Rule.FAILURE_STRING, text, suffixes))); } + + return failures; }) ) ); diff --git a/src/contextualLifeCycleRule.ts b/src/contextualLifeCycleRule.ts index 61b579148..8f9d98707 100644 --- a/src/contextualLifeCycleRule.ts +++ b/src/contextualLifeCycleRule.ts @@ -26,7 +26,7 @@ export class Rule extends Lint.Rules.AbstractRule { } export class ClassMetadataWalker extends NgWalker { - className: string; + className!: string; isInjectable = false; isComponent = false; isDirective = false; @@ -95,7 +95,7 @@ export class ClassMetadataWalker extends NgWalker { } protected visitNgInjectable(controller: ts.ClassDeclaration, decorator: ts.Decorator) { - this.className = controller.name.text; + this.className = controller.name!.text; this.isInjectable = true; this.isComponent = false; this.isDirective = false; @@ -104,7 +104,7 @@ export class ClassMetadataWalker extends NgWalker { } protected visitNgComponent(metadata: ComponentMetadata) { - this.className = metadata.controller.name.text; + this.className = metadata.controller.name!.text; this.isComponent = true; this.isInjectable = false; this.isDirective = false; @@ -113,7 +113,7 @@ export class ClassMetadataWalker extends NgWalker { } protected visitNgDirective(metadata: DirectiveMetadata) { - this.className = metadata.controller.name.text; + this.className = metadata.controller.name!.text; this.isDirective = true; this.isInjectable = false; this.isComponent = false; @@ -122,7 +122,7 @@ export class ClassMetadataWalker extends NgWalker { } protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) { - this.className = controller.name.text; + this.className = controller.name!.text; this.isPipe = true; this.isInjectable = false; this.isComponent = false; diff --git a/src/decoratorNotAllowedRule.ts b/src/decoratorNotAllowedRule.ts index 0c702df75..60bcbb4be 100644 --- a/src/decoratorNotAllowedRule.ts +++ b/src/decoratorNotAllowedRule.ts @@ -26,11 +26,11 @@ export class Rule extends Lint.Rules.AbstractRule { } export class ClassMetadataWalker extends NgWalker { - className: string; + className!: string; isInjectable = false; protected visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) { - this.className = classDeclaration.name.text; + this.className = classDeclaration.name!.text; this.isInjectable = true; super.visitNgInjectable(classDeclaration, decorator); } diff --git a/src/directiveClassSuffixRule.ts b/src/directiveClassSuffixRule.ts index 417e7a630..7c042b85a 100644 --- a/src/directiveClassSuffixRule.ts +++ b/src/directiveClassSuffixRule.ts @@ -2,19 +2,10 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; import { NgWalker } from './angular/ngWalker'; +import { getInterfaceName } from './util/utils'; import { DirectiveMetadata } from './angular/metadata'; -const getInterfaceName = (t: any): string => { - if (!t.expression) { - return ''; - } - if (t.expression.name) { - return t.expression.name.text; - } - return t.expression.text; -}; - const ValidatorSuffix = 'Validator'; export class Rule extends Lint.Rules.AbstractRule { @@ -49,26 +40,30 @@ export class Rule extends Lint.Rules.AbstractRule { export class ClassMetadataWalker extends NgWalker { protected visitNgDirective(metadata: DirectiveMetadata) { - let name = metadata.controller.name; - let className: string = name.text; + const name = metadata.controller.name!; + const className = name.text; const options = this.getOptions(); const suffixes: string[] = options.length ? options : ['Directive']; - const heritageClauses = metadata.controller.heritageClauses; + const { heritageClauses } = metadata.controller; + if (heritageClauses) { const i = heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword); + if ( i.length !== 0 && i[0].types .map(getInterfaceName) - .filter(name => !!name) - .some(name => name.endsWith(ValidatorSuffix)) + .filter(x => !!x) + .some(x => x.endsWith(ValidatorSuffix)) ) { suffixes.push(ValidatorSuffix); } } + if (!Rule.validate(className, suffixes)) { this.addFailureAtNode(name, sprintf(Rule.FAILURE_STRING, className, suffixes.join(', '))); } + super.visitNgDirective(metadata); } } diff --git a/src/enforceComponentSelectorRule.ts b/src/enforceComponentSelectorRule.ts index 239672d21..e16f68946 100644 --- a/src/enforceComponentSelectorRule.ts +++ b/src/enforceComponentSelectorRule.ts @@ -25,8 +25,9 @@ export class Rule extends Lint.Rules.AbstractRule { export class EnforceComponentSelectorValidatorWalker extends NgWalker { protected visitNgComponent(metadata: ComponentMetadata) { if (!metadata.selector) { - this.addFailureAtNode(metadata.decorator, sprintf(Rule.SELECTOR_FAILURE, metadata.controller.name.text)); + this.addFailureAtNode(metadata.decorator, sprintf(Rule.SELECTOR_FAILURE, metadata.controller.name!.text)); } + super.visitNgComponent(metadata); } } diff --git a/src/i18nRule.ts b/src/i18nRule.ts index eb796a321..6e4d803fe 100644 --- a/src/i18nRule.ts +++ b/src/i18nRule.ts @@ -44,7 +44,7 @@ class I18NTextVisitor extends BasicTemplateAstVisitor implements ConfigurableVis static Error = 'Each element containing text node should have an i18n attribute'; private hasI18n = false; - private nestedElements = []; + private nestedElements: string[] = []; private visited = new Set(); visitText(text: ast.TextAst, context: BasicTemplateAstVisitor) { @@ -109,7 +109,7 @@ class I18NTemplateVisitor extends BasicTemplateAstVisitor { ]; visit(a: any, context: any) { - super.visit(a, context); + super.visit!(a, context); } visitAttr(attr: ast.AttrAst, context: any): any { diff --git a/src/importDestructuringSpacingRule.ts b/src/importDestructuringSpacingRule.ts index 638d56fb2..e25745376 100644 --- a/src/importDestructuringSpacingRule.ts +++ b/src/importDestructuringSpacingRule.ts @@ -65,8 +65,8 @@ class ImportDestructuringSpacingWalker extends RuleWalker { return; } - const totalLeadingSpaces = nodeText.match(/^\{(\s*)/)[1].length; - const totalTrailingSpaces = nodeText.match(/(\s*)}$/)[1].length; + const totalLeadingSpaces = nodeText.match(/^\{(\s*)/)![1].length; + const totalTrailingSpaces = nodeText.match(/(\s*)}$/)![1].length; if (totalLeadingSpaces === 1 && totalTrailingSpaces === 1) { return; diff --git a/src/maxInlineDeclarationsRule.ts b/src/maxInlineDeclarationsRule.ts index 5404e2f25..06fcad574 100644 --- a/src/maxInlineDeclarationsRule.ts +++ b/src/maxInlineDeclarationsRule.ts @@ -45,7 +45,7 @@ export class Rule extends Rules.AbstractRule { static readonly FAILURE_STRING = 'Exceeds the maximum allowed inline lines for %s. Defined limit: %s / total lines: %s (https://angular.io/guide/styleguide#style-05-04)'; apply(sourceFile: SourceFile): RuleFailure[] { - return this.applyWithWalker(new MaxInlineDeclarationsValidator(sourceFile, this.getOptions())); + return this.applyWithWalker(new MaxInlineDeclarationsWalker(sourceFile, this.getOptions())); } isEnabled(): boolean { @@ -76,7 +76,7 @@ export const getTemplateFailure = (value: number, limit = DEFAULT_TEMPLATE_LIMIT return generateFailure(OPTION_TEMPLATE, limit, value); }; -export class MaxInlineDeclarationsValidator extends NgWalker { +export class MaxInlineDeclarationsWalker extends NgWalker { private readonly stylesLinesLimit = DEFAULT_STYLES_LIMIT; private readonly templateLinesLimit = DEFAULT_TEMPLATE_LIMIT; private readonly newLineRegExp = /\r\n|\r|\n/; @@ -84,7 +84,7 @@ export class MaxInlineDeclarationsValidator extends NgWalker { constructor(sourceFile: SourceFile, options: IOptions) { super(sourceFile, options); - const { styles, template } = (options.ruleArguments[0] || []) as PropertyPair; + const { styles = -1, template = -1 } = (options.ruleArguments[0] || []) as PropertyPair; this.stylesLinesLimit = styles > -1 ? styles : this.stylesLinesLimit; this.templateLinesLimit = template > -1 ? template : this.templateLinesLimit; @@ -96,73 +96,50 @@ export class MaxInlineDeclarationsValidator extends NgWalker { super.visitNgComponent(metadata); } - private getTotalLines(source: CodeWithSourceMap['source']): number { - return source.trim().split(this.newLineRegExp).length; + private getLinesCount(source: CodeWithSourceMap['source']): number { + return source!.trim().split(this.newLineRegExp).length; } - private getTemplateLinesCount(metadata: ComponentMetadata): number { - return this.hasInlineTemplate(metadata) ? this.getTotalLines(metadata.template.template.source) : 0; - } + private getInlineStylesLinesCount(metadata: ComponentMetadata): number { + return (metadata.styles || []).reduce((previousValue, currentValue) => { + if (!currentValue.url) { + previousValue += this.getLinesCount(currentValue.style.source); + } - private hasInlineTemplate(metadata: ComponentMetadata): boolean { - return !!(metadata.template && !metadata.template.url && metadata.template.template && metadata.template.template.source); + return previousValue; + }, 0); } - private validateInlineTemplate(metadata: ComponentMetadata): void { - const templateLinesCount = this.getTemplateLinesCount(metadata); + private validateInlineStyles(metadata: ComponentMetadata): void { + const linesCount = this.getInlineStylesLinesCount(metadata); - if (templateLinesCount <= this.templateLinesLimit) { + if (linesCount <= this.stylesLinesLimit) { return; } - const failureMessage = getTemplateFailure(templateLinesCount, this.templateLinesLimit); + const failureMessage = getStylesFailure(linesCount, this.stylesLinesLimit); - this.addFailureAtNode(metadata.template.node, failureMessage); - } - - private getInlineStylesLinesCount(metadata: ComponentMetadata): number { - let result = 0; - - if (!this.hasInlineStyles(metadata)) { - return result; - } - - for (let i = 0; i < metadata.styles.length; i++) { - if (!metadata.styles[i].url) { - result += this.getTotalLines(metadata.styles[i].style.source); - } + for (const style of metadata.styles) { + this.addFailureAtNode(style.node!, failureMessage); } - - return result; + } + private getTemplateLinesCount(metadata: ComponentMetadata): number { + return this.hasInlineTemplate(metadata) ? this.getLinesCount(metadata.template.template.source) : 0; } - private hasInlineStyles(metadata: ComponentMetadata): boolean { - if (!metadata.styles) { - return false; - } - - for (let i = 0; i < metadata.styles.length; i++) { - const style = metadata.styles[i]; - - if (!style.url && style.style && style.style.source) { - return true; - } - } - - return false; + private hasInlineTemplate(metadata: ComponentMetadata): boolean { + return !!(metadata.template && !metadata.template.url && metadata.template.template && metadata.template.template.source); } - private validateInlineStyles(metadata: ComponentMetadata): void { - const stylesLinesCount = this.getInlineStylesLinesCount(metadata); + private validateInlineTemplate(metadata: ComponentMetadata): void { + const linesCount = this.getTemplateLinesCount(metadata); - if (stylesLinesCount <= this.stylesLinesLimit) { + if (linesCount <= this.templateLinesLimit) { return; } - const failureMessage = getStylesFailure(stylesLinesCount, this.stylesLinesLimit); + const failureMessage = getTemplateFailure(linesCount, this.templateLinesLimit); - for (let i = 0; i < metadata.styles.length; i++) { - this.addFailureAtNode(metadata.styles[i].node, failureMessage); - } + this.addFailureAtNode(metadata.template.node!, failureMessage); } } diff --git a/src/noAttributeParameterDecoratorRule.ts b/src/noAttributeParameterDecoratorRule.ts index 6015e4a5b..dd8714179 100644 --- a/src/noAttributeParameterDecoratorRule.ts +++ b/src/noAttributeParameterDecoratorRule.ts @@ -1,64 +1,60 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; -import SyntaxKind = require('./util/syntaxKind'); import { validate, all } from './walkerFactory/walkerFn'; import { Maybe, listToMaybe } from './util/function'; -import { isDecorator, withIdentifier, callExpression } from './util/astQuery'; +import { withIdentifier, callExpression } from './util/astQuery'; import { Failure } from './walkerFactory/walkerFactory'; export class Rule extends Lint.Rules.AbstractRule { - public static metadata: Lint.IRuleMetadata = { - ruleName: 'no-attribute-parameter-decorator', - type: 'maintainability', + static readonly metadata: Lint.IRuleMetadata = { description: 'Disallow usage of @Attribute decorator.', - rationale: '@Attribute is considered bad practice. Use @Input instead.', options: null, optionsDescription: 'Not configurable.', + rationale: '@Attribute is considered bad practice. Use @Input instead.', + ruleName: 'no-attribute-parameter-decorator', + type: 'maintainability', typescriptOnly: true }; - static FAILURE_STRING = 'In the constructor of class "%s",' + + static readonly FAILURE_STRING = 'In the constructor of class "%s",' + ' the parameter "%s" uses the @Attribute decorator, ' + 'which is considered as a bad practice. Please,' + ' consider construction of type "@Input() %s: string"'; - private static walkerBuilder = all( - validate(SyntaxKind.current().Constructor)((node: ts.ConstructorDeclaration) => { - const syntaxKind = SyntaxKind.current(); + private static readonly walkerBuilder = all( + validate(ts.SyntaxKind.Constructor)(node => { return Maybe.lift(node.parent) .fmap(parent => { - if (parent.kind === syntaxKind.ClassExpression && parent.parent.name) { - return parent.parent.name.text; - } else if (parent.kind === syntaxKind.ClassDeclaration) { - return parent.name.text; + if (parent!.kind === ts.SyntaxKind.ClassExpression && (parent!.parent as any).name) { + return (parent!.parent as any).name.text; + } else if (parent!.kind === ts.SyntaxKind.ClassDeclaration) { + return (parent as any).name!.text; } }) .bind(parentName => { - const failures: Maybe[] = node.parameters.map(p => - Maybe.lift(p.decorators).bind(decorators => { + const failures: Maybe[] = (node as any).parameters.map(p => { + const text = p.name.getText(); + + return Maybe.lift(p.decorators).bind(decorators => { // Check if any @Attribute - const decoratorsFailed = listToMaybe(decorators.map(d => Rule.decoratorIsAttribute(d))); + const decoratorsFailed = listToMaybe(decorators!.map(d => Rule.decoratorIsAttribute(d))); // We only care about 1 since we highlight the whole 'parameter' - return decoratorsFailed.fmap( - () => new Failure(p, sprintf(Rule.FAILURE_STRING, parentName, (p.name).text, (p.name).text)) - ); - }) - ); - return listToMaybe(failures); + return (decoratorsFailed as any).fmap(() => new Failure(p, sprintf(Rule.FAILURE_STRING, parentName, text, text))); + }); + }); + + return listToMaybe(failures) as any; }); }) ); - private static decoratorIsAttribute(dec: ts.Decorator): Maybe { - if (isDecorator(dec)) { - return callExpression(dec).bind(withIdentifier('Attribute')); - } - return Maybe.nothing; + private static decoratorIsAttribute(dec: ts.Decorator): Maybe { + return callExpression(dec).bind(withIdentifier('Attribute') as any); } - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(Rule.walkerBuilder(sourceFile, this.getOptions())); } } diff --git a/src/noConflictingLifeCycleHooksRule.ts b/src/noConflictingLifeCycleHooksRule.ts index 5e06ed0ae..a07dcef91 100644 --- a/src/noConflictingLifeCycleHooksRule.ts +++ b/src/noConflictingLifeCycleHooksRule.ts @@ -51,18 +51,18 @@ export class ClassMetadataWalker extends Lint.RuleWalker { const matchesAllHooks = lifecycleHooksMethods.every(l => interfaces.indexOf(l) !== -1); if (matchesAllHooks) { - this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name.text)); + this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name!.text)); } } private validateMethods(node: ts.ClassDeclaration): void { - const methodNames = node.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration).map(m => m.name.getText()); + const methodNames = node.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration).map(m => m.name!.getText()); const matchesAllHooks = lifecycleHooksMethods.every(l => { return methodNames.indexOf(`${hooksPrefix}${l}`) !== -1; }); if (matchesAllHooks) { - this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name.text)); + this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name!.text)); } } } diff --git a/src/noForwardRefRule.ts b/src/noForwardRefRule.ts index 0f279b107..d0188c143 100644 --- a/src/noForwardRefRule.ts +++ b/src/noForwardRefRule.ts @@ -1,30 +1,28 @@ -import * as Lint from 'tslint'; -import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; -import SyntaxKind = require('./util/syntaxKind'); +import { IRuleMetadata, RuleFailure, Rules, RuleWalker } from 'tslint/lib'; +import { CallExpression, SourceFile, SyntaxKind } from 'typescript/lib/typescript'; -export class Rule extends Lint.Rules.AbstractRule { - public static metadata: Lint.IRuleMetadata = { - ruleName: 'no-forward-ref', - type: 'maintainability', +export class Rule extends Rules.AbstractRule { + static metadata: IRuleMetadata = { description: 'Disallows usage of forward references for DI.', - rationale: 'The flow of DI is disrupted by using `forwardRef` and might make code more difficult to understand.', options: null, optionsDescription: 'Not configurable.', + rationale: 'The flow of DI is disrupted by using `forwardRef` and might make code more difficult to understand.', + ruleName: 'no-forward-ref', + type: 'maintainability', typescriptOnly: true }; - static FAILURE_IN_CLASS: string = 'Avoid using forwardRef in class "%s"'; - - static FAILURE_IN_VARIABLE: string = 'Avoid using forwardRef in variable "%s"'; + static readonly FAILURE_STRING_CLASS = 'Avoid using forwardRef in class "%s"'; + static readonly FAILURE_STRING_VARIABLE = 'Avoid using forwardRef in variable "%s"'; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + apply(sourceFile: SourceFile): RuleFailure[] { return this.applyWithWalker(new ExpressionCallMetadataWalker(sourceFile, this.getOptions())); } } -export class ExpressionCallMetadataWalker extends Lint.RuleWalker { - visitCallExpression(node: ts.CallExpression) { +export class ExpressionCallMetadataWalker extends RuleWalker { + visitCallExpression(node: CallExpression) { this.validateCallExpression(node); super.visitCallExpression(node); } @@ -32,16 +30,16 @@ export class ExpressionCallMetadataWalker extends Lint.RuleWalker { private validateCallExpression(callExpression) { if (callExpression.expression.text === 'forwardRef') { let currentNode = callExpression; + while (currentNode.parent.parent) { currentNode = currentNode.parent; } - let failure: string; - if (currentNode.kind === SyntaxKind.current().VariableStatement) { - failure = sprintf(Rule.FAILURE_IN_VARIABLE, currentNode.declarationList.declarations[0].name.text); - } else { - failure = sprintf(Rule.FAILURE_IN_CLASS, currentNode.name.text); - } + const failure = + currentNode.kind === SyntaxKind.VariableStatement + ? sprintf(Rule.FAILURE_STRING_VARIABLE, currentNode.declarationList.declarations[0].name.text) + : sprintf(Rule.FAILURE_STRING_CLASS, currentNode.name.text); + this.addFailureAtNode(callExpression, failure); } } diff --git a/src/noInputRenameRule.ts b/src/noInputRenameRule.ts index e7c5bb4c3..36f18597c 100644 --- a/src/noInputRenameRule.ts +++ b/src/noInputRenameRule.ts @@ -30,7 +30,7 @@ export const getFailureMessage = (className: string, propertyName: string): stri }; export class InputMetadataWalker extends NgWalker { - private directiveSelectors: ReadonlySet; + private directiveSelectors!: ReadonlySet; protected visitNgDirective(metadata: DirectiveMetadata): void { this.directiveSelectors = new Set((metadata.selector || '').replace(/[\[\]\s]/g, '').split(',')); diff --git a/src/noOutputNamedAfterStandardEventRule.ts b/src/noOutputNamedAfterStandardEventRule.ts index 9d9e411a7..26e199eb8 100644 --- a/src/noOutputNamedAfterStandardEventRule.ts +++ b/src/noOutputNamedAfterStandardEventRule.ts @@ -198,14 +198,15 @@ export class OutputMetadataWalker extends NgWalker { ]); protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) { - let className = (property).parent.name.text; - let memberName = (property.name).text; - let outputName = args.length === 0 ? memberName : args[0]; + const className = (property.parent as any).name.text; + const memberName = (property.name as any).text; + const outputName = args.length === 0 ? memberName : args[0]; if (outputName && this.standardEventNames.get(outputName)) { - const errorMessage = sprintf(Rule.FAILURE_STRING, className, memberName); - this.addFailureAtNode(property, errorMessage); + const failure = sprintf(Rule.FAILURE_STRING, className, memberName); + this.addFailureAtNode(property, failure); } + super.visitNgOutput(property, output, args); } } diff --git a/src/noOutputRenameRule.ts b/src/noOutputRenameRule.ts index 9fab91b17..e842bf020 100644 --- a/src/noOutputRenameRule.ts +++ b/src/noOutputRenameRule.ts @@ -27,7 +27,7 @@ export const getFailureMessage = (): string => { }; export class OutputMetadataWalker extends NgWalker { - private directiveSelectors: ReadonlySet; + private directiveSelectors!: ReadonlySet; visitNgDirective(metadata: DirectiveMetadata): void { this.directiveSelectors = new Set((metadata.selector || '').replace(/[\[\]\s]/g, '').split(',')); diff --git a/src/noQueriesParameterRule.ts b/src/noQueriesParameterRule.ts index 18b9c941a..c5a8e9a57 100644 --- a/src/noQueriesParameterRule.ts +++ b/src/noQueriesParameterRule.ts @@ -1,8 +1,8 @@ -import * as Lint from 'tslint'; +import { IOptions, IRuleMetadata } from 'tslint/lib'; import { UsePropertyDecorator } from './propertyDecoratorBase'; export class Rule extends UsePropertyDecorator { - static metadata: Lint.IRuleMetadata = { + static readonly metadata: IRuleMetadata = { description: 'Use @ContentChild, @ContentChildren, @ViewChild or @ViewChildren instead of the `queries` property of ' + '`@Component` or `@Directive` metadata.', @@ -18,14 +18,14 @@ export class Rule extends UsePropertyDecorator { typescriptOnly: true }; - static FAILURE_STRING = 'Use @ContentChild, @ContentChildren, @ViewChild or @ViewChildren instead of the queries property'; + static readonly FAILURE_STRING = 'Use @ContentChild, @ContentChildren, @ViewChild or @ViewChildren instead of the queries property'; - constructor(options: Lint.IOptions) { + constructor(options: IOptions) { super( { decoratorName: ['ContentChild', 'ContentChildren', 'ViewChild', 'ViewChildren'], - propertyName: 'queries', - errorMessage: Rule.FAILURE_STRING + errorMessage: Rule.FAILURE_STRING, + propertyName: 'queries' }, options ); diff --git a/src/noTemplateCallExpressionRule.ts b/src/noTemplateCallExpressionRule.ts index 4e28f1c7f..2206de621 100644 --- a/src/noTemplateCallExpressionRule.ts +++ b/src/noTemplateCallExpressionRule.ts @@ -20,8 +20,8 @@ export class Rule extends Lint.Rules.AbstractRule { apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { const walkerConfig: NgWalkerConfig = { - templateVisitorCtrl: TemplateVisitor, - expressionVisitorCtrl: ExpressionVisitor + expressionVisitorCtrl: ExpressionVisitor, + templateVisitorCtrl: TemplateVisitor }; return this.applyWithWalker(new NgWalker(sourceFile, this.getOptions(), walkerConfig)); diff --git a/src/noUnusedCssRule.ts b/src/noUnusedCssRule.ts index 8cba00d20..8254405ca 100644 --- a/src/noUnusedCssRule.ts +++ b/src/noUnusedCssRule.ts @@ -61,11 +61,7 @@ const lang = require('cssauron')({ .map(b => b.name) .join(' '); const classAttr = node.attrs.filter(a => a.name.toLowerCase() === 'class').pop(); - let staticClasses = ''; - if (classAttr) { - staticClasses = classAttr.value + ' '; - } - return staticClasses + classBindings; + return classAttr ? `${classAttr.value} ${classBindings}` : classBindings; }, parent(node: any) { return node.parentNode; @@ -75,10 +71,8 @@ const lang = require('cssauron')({ }, attr(node: ElementAst, attr: string) { const targetAttr = node.attrs.filter(a => a.name === attr).pop(); - if (targetAttr) { - return targetAttr.value; - } - return undefined; + + return targetAttr ? targetAttr.value : undefined; } }); @@ -90,7 +84,7 @@ class ElementVisitor extends BasicTemplateAstVisitor { if (c instanceof ElementAst) { (c).parentNode = ast; } - this.visit(c, fn); + this.visit!(c, fn); }); } } @@ -161,7 +155,7 @@ export class Rule extends Lint.Rules.AbstractRule { } class UnusedCssVisitor extends BasicCssAstVisitor { - templateAst: TemplateAst; + templateAst!: TemplateAst; constructor( sourceFile: ts.SourceFile, @@ -193,10 +187,10 @@ class UnusedCssVisitor extends BasicCssAstVisitor { } visitCssSelector(ast: CssSelectorAst) { - const parts = []; + const parts: string[] = []; for (let i = 0; i < ast.selectorParts.length; i += 1) { const c = ast.selectorParts[i]; - c.strValue = c.strValue.split('::').shift(); + c.strValue = c.strValue.split('::').shift()!; // Stop on /deep/ and >>> if (c.strValue.endsWith('/') || c.strValue.endsWith('>')) { parts.push(c.strValue); @@ -212,13 +206,15 @@ class UnusedCssVisitor extends BasicCssAstVisitor { const strippedSelector = parts.map(s => s.replace(/\/|>$/, '').trim()).join(' '); const elementFilterVisitor = new ElementFilterVisitor(this.getSourceFile(), this._originalOptions, this.context, 0); const tokenized = CssSelectorTokenizer.parse(strippedSelector); - const selectorTypesCache = Object.keys(dynamicFilters).reduce((a: any, key: string) => { + const selectorTypesCache = Object.keys(dynamicFilters).reduce((a, key) => { a[key] = hasSelector(tokenized, key); return a; }, {}); + if (!elementFilterVisitor.shouldVisit(this.templateAst, dynamicFilters, selectorTypesCache)) { return true; } + let matchFound = false; const selector = (element: ElementAst) => { if (lang(strippedSelector)(element)) { @@ -228,19 +224,19 @@ class UnusedCssVisitor extends BasicCssAstVisitor { return false; }; const visitor = new ElementVisitor(this.getSourceFile(), this._originalOptions, this.context, 0); - visitor.visit(this.templateAst, selector); + visitor.visit!(this.templateAst, selector); return matchFound; } } // Finds the template and wrapes the parsed content into a root element export class UnusedCssNgVisitor extends NgWalker { - private templateAst: TemplateAst; + private templateAst!: TemplateAst; visitClassDeclaration(declaration: ts.ClassDeclaration) { const d = getComponentDecorator(declaration); if (d) { - const meta = this._metadataReader.read(declaration); + const meta = this._metadataReader!.read(declaration); this.visitNgComponent(meta); if (meta.template && meta.template.template) { try { @@ -295,7 +291,7 @@ export class UnusedCssNgVisitor extends NgWalker { return; } - const file = this.getContextSourceFile(styleMetadata.url, styleMetadata.style.source); + const file = this.getContextSourceFile(styleMetadata.url!, styleMetadata.style.source!); const visitor = new UnusedCssVisitor(file, this._originalOptions, context, styleMetadata, baseStart); visitor.templateAst = this.templateAst; const d = getComponentDecorator(context.controller); diff --git a/src/pipeImpureRule.ts b/src/pipeImpureRule.ts index 77dbf164c..de9343497 100644 --- a/src/pipeImpureRule.ts +++ b/src/pipeImpureRule.ts @@ -2,7 +2,6 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; import { NgWalker } from './angular/ngWalker'; -import SyntaxKind = require('./util/syntaxKind'); export class Rule extends Lint.Rules.AbstractRule { public static metadata: Lint.IRuleMetadata = { @@ -24,13 +23,13 @@ export class Rule extends Lint.Rules.AbstractRule { export class ClassMetadataWalker extends NgWalker { protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) { - this.validateProperties(controller.name.text, decorator); + this.validateProperties(controller.name!.text, decorator); super.visitNgPipe(controller, decorator); } private validateProperties(className: string, pipe: any) { let argument = this.extractArgument(pipe); - if (argument.kind === SyntaxKind.current().ObjectLiteralExpression) { + if (argument.kind === ts.SyntaxKind.ObjectLiteralExpression) { argument.properties.filter(n => n.name.text === 'pure').forEach(this.validateProperty.bind(this, className)); } } diff --git a/src/pipeNamingRule.ts b/src/pipeNamingRule.ts index 283919126..f4af26494 100644 --- a/src/pipeNamingRule.ts +++ b/src/pipeNamingRule.ts @@ -5,7 +5,6 @@ import { NgWalker } from './angular/ngWalker'; import { SelectorValidator } from './util/selectorValidator'; const OPTION_ATTRIBUTE = 'attribute'; -const OPTION_ELEMENT = 'element'; const OPTION_CAMEL_CASE = 'camelCase'; const OPTION_KEBAB_CASE = 'kebab-case'; @@ -44,10 +43,10 @@ export class Rule extends Lint.Rules.AbstractRule { static FAILURE_WITH_PREFIX = `The name of the Pipe decorator of class %s should be named ${OPTION_CAMEL_CASE} with prefix %s, however its value is "%s"`; - prefix: string; - hasPrefix: boolean; - private prefixChecker: Function; - private validator: Function; + prefix!: string; + hasPrefix!: boolean; + private prefixChecker!: Function; + private validator!: Function; constructor(options: Lint.IOptions) { super(options); @@ -61,7 +60,7 @@ export class Rule extends Lint.Rules.AbstractRule { } if (args.length > 1) { this.hasPrefix = true; - let prefixExpression: string = (args.slice(1) || []).join('|'); + let prefixExpression = (args.slice(1) || []).join('|'); this.prefix = (args.slice(1) || []).join(','); this.prefixChecker = SelectorValidator.prefix(prefixExpression, OPTION_CAMEL_CASE); } @@ -97,7 +96,7 @@ export class ClassMetadataWalker extends NgWalker { } protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) { - let className = controller.name.text; + let className = controller.name!.text; this.validateProperties(className, decorator); super.visitNgPipe(controller, decorator); } diff --git a/src/preferInlineDecoratorRule.ts b/src/preferInlineDecoratorRule.ts index f5ceba907..3e0172b7b 100644 --- a/src/preferInlineDecoratorRule.ts +++ b/src/preferInlineDecoratorRule.ts @@ -74,17 +74,17 @@ export class PreferInlineDecoratorWalker extends NgWalker { } protected visitMethodDecorator(decorator: Decorator) { - this.validateDecorator(decorator, decorator.parent); + this.validateDecorator(decorator, decorator.parent!); super.visitMethodDecorator(decorator); } protected visitPropertyDecorator(decorator: Decorator) { - this.validateDecorator(decorator, decorator.parent); + this.validateDecorator(decorator, decorator.parent!); super.visitPropertyDecorator(decorator); } private validateDecorator(decorator: Decorator, property: Node) { - const decoratorName: DecoratorKeys = getDecoratorName(decorator); + const decoratorName = getDecoratorName(decorator) as DecoratorKeys; const isDecoratorBlacklisted = this.blacklistedDecorators.has(decoratorName); if (isDecoratorBlacklisted) { diff --git a/src/propertyDecoratorBase.ts b/src/propertyDecoratorBase.ts index 57db89cd3..96f033edb 100644 --- a/src/propertyDecoratorBase.ts +++ b/src/propertyDecoratorBase.ts @@ -2,7 +2,6 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; import { IOptions } from 'tslint'; -import SyntaxKind = require('./util/syntaxKind'); export interface IUsePropertyDecoratorConfig { propertyName: string; @@ -36,7 +35,7 @@ class DirectiveMetadataWalker extends Lint.RuleWalker { } visitClassDeclaration(node: ts.ClassDeclaration) { - (>node.decorators).forEach(this.validateDecorator.bind(this, node.name.text)); + (>node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text)); super.visitClassDeclaration(node); } @@ -52,8 +51,8 @@ class DirectiveMetadataWalker extends Lint.RuleWalker { } private validateProperty(className: string, decoratorName: string, arg: ts.ObjectLiteralExpression) { - if (arg.kind === SyntaxKind.current().ObjectLiteralExpression) { - arg.properties.filter(prop => prop.name.getText() === this.config.propertyName).forEach(prop => { + if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { + arg.properties.filter(prop => prop.name!.getText() === this.config.propertyName).forEach(prop => { this.addFailureAtNode(prop, UsePropertyDecorator.formatFailureString(this.config, decoratorName, className)); }); } diff --git a/src/selectorNameBase.ts b/src/selectorNameBase.ts index 0d62ad3ad..8a075a7d7 100644 --- a/src/selectorNameBase.ts +++ b/src/selectorNameBase.ts @@ -3,20 +3,18 @@ import { SelectorValidator } from './util/selectorValidator'; import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; import * as compiler from '@angular/compiler'; -import { IOptions } from 'tslint'; -import SyntaxKind = require('./util/syntaxKind'); export type SelectorType = 'element' | 'attribute'; export type SelectorTypeInternal = 'element' | 'attrs'; export type SelectorStyle = 'kebab-case' | 'camelCase'; export abstract class SelectorRule extends Lint.Rules.AbstractRule { - handleType: string; + handleType!: string; prefixes: string[]; types: SelectorTypeInternal[]; style: SelectorStyle[]; - constructor(options: IOptions) { + constructor(options: Lint.IOptions) { super(options); const args = this.getOptions().ruleArguments; @@ -104,7 +102,7 @@ export class SelectorValidatorWalker extends Lint.RuleWalker { visitClassDeclaration(node: ts.ClassDeclaration) { if (node.decorators) { - (>node.decorators).forEach(this.validateDecorator.bind(this, node.name.text)); + (>node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text)); } super.visitClassDeclaration(node); } @@ -122,7 +120,7 @@ export class SelectorValidatorWalker extends Lint.RuleWalker { } private validateSelector(className: string, arg: ts.Node) { - if (arg.kind === SyntaxKind.current().ObjectLiteralExpression) { + if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { (arg).properties .filter(prop => this.validateProperty(prop)) .map(prop => (prop).initializer) @@ -151,8 +149,7 @@ export class SelectorValidatorWalker extends Lint.RuleWalker { } private isSupportedKind(kind: number): boolean { - const current = SyntaxKind.current(); - return [current.StringLiteral, current.NoSubstitutionTemplateLiteral].some(kindType => kindType === kind); + return [ts.SyntaxKind.StringLiteral, ts.SyntaxKind.NoSubstitutionTemplateLiteral].some(kindType => kindType === kind); } private extractMainSelector(i: any) { diff --git a/src/templateConditionalComplexityRule.ts b/src/templateConditionalComplexityRule.ts index 87769c0f9..3fae81d16 100644 --- a/src/templateConditionalComplexityRule.ts +++ b/src/templateConditionalComplexityRule.ts @@ -52,7 +52,7 @@ export const getFailureMessage = (totalComplexity: number, maxComplexity = Rule. }; const getTotalComplexity = (ast: AST): number => { - const expr = (ast as ASTWithSource).source.replace(/\s/g, ''); + const expr = ((ast as ASTWithSource).source || '').replace(/\s/g, ''); const expressionParser = new Parser(new Lexer()); const astWithSource = expressionParser.parseAction(expr, null); const conditions: Binary[] = []; @@ -65,7 +65,7 @@ const getTotalComplexity = (ast: AST): number => { } while (conditions.length > 0) { - condition = conditions.pop(); + condition = conditions.pop()!; if (!condition.operation) { continue; diff --git a/src/templatesNoNegatedAsyncRule.ts b/src/templatesNoNegatedAsyncRule.ts index 64bd21b31..403b1490e 100644 --- a/src/templatesNoNegatedAsyncRule.ts +++ b/src/templatesNoNegatedAsyncRule.ts @@ -26,7 +26,7 @@ class TemplateToNgTemplateVisitor extends RecursiveAngularExpressionVisitor { span: { end: spanEnd, start: spanStart } } = expr; const operator = this.codeWithMap.code.slice(endLeftSpan, startRightSpan); - const operatorStart = /^.*==/.exec(operator)[0].length - unstrictEqualityOperator.length; + const operatorStart = /^.*==/.exec(operator)![0].length - unstrictEqualityOperator.length; this.addFailureFromStartToEnd(spanStart, spanEnd, 'Async pipes must use strict equality `===` when comparing with `false`', [ new Lint.Replacement(this.getSourcePosition(endLeftSpan) + operatorStart, unstrictEqualityOperator.length, '===') @@ -45,7 +45,7 @@ class TemplateToNgTemplateVisitor extends RecursiveAngularExpressionVisitor { // Angular includes the whitespace after an expression, we want to trim that const expressionSource = this.codeWithMap.code.slice(spanStart, spanEnd); - const concreteWidth = spanEnd - spanStart - / *$/.exec(expressionSource)[0].length; + const concreteWidth = spanEnd - spanStart - / *$/.exec(expressionSource)![0].length; this.addFailureFromStartToEnd(spanStart, spanEnd, 'Async pipes can not be negated, use (observable | async) === false instead', [ new Lint.Replacement(absoluteStart + concreteWidth, 1, ' === false '), diff --git a/src/useHostPropertyDecoratorRule.ts b/src/useHostPropertyDecoratorRule.ts index 372a0307d..75fdca45d 100644 --- a/src/useHostPropertyDecoratorRule.ts +++ b/src/useHostPropertyDecoratorRule.ts @@ -1,30 +1,30 @@ -import * as Lint from 'tslint'; - +import { IOptions, IRuleMetadata } from 'tslint/lib'; import { UsePropertyDecorator } from './propertyDecoratorBase'; -import { IOptions } from 'tslint'; export class Rule extends UsePropertyDecorator { - public static metadata: Lint.IRuleMetadata = { - ruleName: 'use-host-property-decorator', - type: 'style', + static readonly metadata: IRuleMetadata = { description: 'Use @HostProperty decorator rather than the `host` property of `@Component` and `@Directive` metadata.', descriptionDetails: 'See more at https://angular.io/styleguide#style-06-03.', + options: null, + optionsDescription: 'Not configurable.', rationale: 'The property associated with `@HostBinding` or the method associated with `@HostListener` ' + "can be modified only in a single place: in the directive's class. If you use the `host` metadata " + 'property, you must modify both the property declaration inside the controller, and the metadata ' + 'associated with the directive.', - options: null, - optionsDescription: 'Not configurable.', + ruleName: 'use-host-property-decorator', + type: 'style', typescriptOnly: true }; + static readonly FAILURE_STRING = 'Use @HostBindings and @HostListeners instead of the host property (https://angular.io/styleguide#style-06-03)'; + constructor(options: IOptions) { super( { decoratorName: ['HostBindings', 'HostListeners'], - propertyName: 'host', - errorMessage: 'Use @HostBindings and @HostListeners instead of the host property (https://angular.io/styleguide#style-06-03)' + errorMessage: Rule.FAILURE_STRING, + propertyName: 'host' }, options ); diff --git a/src/useInputPropertyDecoratorRule.ts b/src/useInputPropertyDecoratorRule.ts index 7eeb76ad0..208fd9535 100644 --- a/src/useInputPropertyDecoratorRule.ts +++ b/src/useInputPropertyDecoratorRule.ts @@ -1,13 +1,13 @@ -import * as Lint from 'tslint'; +import { IOptions, IRuleMetadata, Utils } from 'tslint/lib'; import { UsePropertyDecorator } from './propertyDecoratorBase'; export class Rule extends UsePropertyDecorator { - static readonly metadata: Lint.IRuleMetadata = { + static readonly metadata: IRuleMetadata = { description: 'Use `@Input` decorator rather than the `inputs` property of `@Component` and `@Directive` metadata.', descriptionDetails: 'See more at https://angular.io/styleguide#style-05-12.', options: null, optionsDescription: 'Not configurable.', - rationale: Lint.Utils.dedent` + rationale: Utils.dedent` * It is easier and more readable to identify which properties in a class are inputs. * If you ever need to rename the property name associated with \`@Input\`, you can modify it in a single place. * The metadata declaration attached to the directive is shorter and thus more readable. @@ -18,11 +18,13 @@ export class Rule extends UsePropertyDecorator { typescriptOnly: true }; - constructor(options: Lint.IOptions) { + static readonly FAILURE_STRING = 'Use the @Input property decorator instead of the inputs property (https://angular.io/styleguide#style-05-12)'; + + constructor(options: IOptions) { super( { decoratorName: 'Input', - errorMessage: 'Use the @Input property decorator instead of the inputs property (https://angular.io/styleguide#style-05-12)', + errorMessage: Rule.FAILURE_STRING, propertyName: 'inputs' }, options diff --git a/src/useLifeCycleInterfaceRule.ts b/src/useLifeCycleInterfaceRule.ts index dfc8977a9..51ca62b9f 100644 --- a/src/useLifeCycleInterfaceRule.ts +++ b/src/useLifeCycleInterfaceRule.ts @@ -1,16 +1,10 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; - -const getInterfaceName = (t: any) => { - if (t.expression && t.expression.name) { - return t.expression.name.text; - } - return t.expression.text; -}; +import { getInterfaceName } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { - static metadata: Lint.IRuleMetadata = { + static readonly metadata: Lint.IRuleMetadata = { description: 'Ensure that components implement life cycle interfaces if they use them.', descriptionDetails: 'See more at https://angular.io/styleguide#style-09-01.', options: null, @@ -21,11 +15,9 @@ export class Rule extends Lint.Rules.AbstractRule { typescriptOnly: true }; - static FAILURE_STRING = 'Implement life cycle hook interface %s for method %s in class %s (https://angular.io/styleguide#style-09-01)'; - - static HOOKS_PREFIX = 'ng'; - - static LIFE_CYCLE_HOOKS_NAMES: string[] = [ + static readonly FAILURE_STRING = 'Implement life cycle hook interface %s for method %s in class %s (https://angular.io/styleguide#style-09-01)'; + static readonly HOOKS_PREFIX = 'ng'; + static readonly LIFE_CYCLE_HOOKS_NAMES: string[] = [ 'OnChanges', 'OnInit', 'DoCheck', @@ -43,9 +35,10 @@ export class Rule extends Lint.Rules.AbstractRule { export class ClassMetadataWalker extends Lint.RuleWalker { visitClassDeclaration(node: ts.ClassDeclaration) { - let className = node.name.text; - let interfaces = this.extractInterfaces(node); - let methods = node.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration); + const className = node.name!.text; + const interfaces = this.extractInterfaces(node); + const methods = node.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration); + this.validateMethods(methods, interfaces, className); super.visitClassDeclaration(node); } @@ -53,7 +46,7 @@ export class ClassMetadataWalker extends Lint.RuleWalker { private extractInterfaces(node: ts.ClassDeclaration): string[] { let interfaces: string[] = []; if (node.heritageClauses) { - let interfacesClause = node.heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword); + const interfacesClause = node.heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword); if (interfacesClause.length !== 0) { interfaces = interfacesClause[0].types.map(getInterfaceName); } @@ -63,19 +56,22 @@ export class ClassMetadataWalker extends Lint.RuleWalker { private validateMethods(methods: ts.ClassElement[], interfaces: string[], className: string) { methods.forEach(m => { - const methodName = m.name.getText(); + const name = m.name!; + const methodName = name.getText(); + if (methodName && this.isMethodValidHook(methodName, interfaces)) { const hookName = methodName.slice(2); - this.addFailureAtNode(m.name, sprintf(Rule.FAILURE_STRING, hookName, Rule.HOOKS_PREFIX + hookName, className)); + this.addFailureAtNode(name, sprintf(Rule.FAILURE_STRING, hookName, Rule.HOOKS_PREFIX + hookName, className)); } }); } private isMethodValidHook(methodName: string, interfaces: string[]): boolean { - const isNg = methodName.substr(0, 2) === Rule.HOOKS_PREFIX; + const isNg = methodName.slice(0, 2) === Rule.HOOKS_PREFIX; const hookName = methodName.slice(2); const isHook = Rule.LIFE_CYCLE_HOOKS_NAMES.indexOf(hookName) !== -1; const isNotIn = interfaces.indexOf(hookName) === -1; + return isNg && isHook && isNotIn; } } diff --git a/src/useOutputPropertyDecoratorRule.ts b/src/useOutputPropertyDecoratorRule.ts index f67e9b145..b3f2eb2d7 100644 --- a/src/useOutputPropertyDecoratorRule.ts +++ b/src/useOutputPropertyDecoratorRule.ts @@ -1,13 +1,13 @@ -import * as Lint from 'tslint'; +import { IOptions, IRuleMetadata, Utils } from 'tslint/lib'; import { UsePropertyDecorator } from './propertyDecoratorBase'; export class Rule extends UsePropertyDecorator { - static readonly metadata: Lint.IRuleMetadata = { + static readonly metadata: IRuleMetadata = { description: 'Use `@Output` decorator rather than the `outputs` property of `@Component` and `@Directive` metadata.', descriptionDetails: 'See more at https://angular.io/styleguide#style-05-12.', options: null, optionsDescription: 'Not configurable.', - rationale: Lint.Utils.dedent` + rationale: Utils.dedent` * It is easier and more readable to identify which properties in a class are events. * If you ever need to rename the event name associated with \`@Output\`, you can modify it in a single place. * The metadata declaration attached to the directive is shorter and thus more readable. @@ -18,11 +18,13 @@ export class Rule extends UsePropertyDecorator { typescriptOnly: true }; - constructor(options: Lint.IOptions) { + static readonly FAILURE_STRING = 'Use the @Output property decorator instead of the outputs property (https://angular.io/styleguide#style-05-12)'; + + constructor(options: IOptions) { super( { decoratorName: 'Output', - errorMessage: 'Use the @Output property decorator instead of the outputs property (https://angular.io/styleguide#style-05-12)', + errorMessage: Rule.FAILURE_STRING, propertyName: 'outputs' }, options diff --git a/src/usePipeDecoratorRule.ts b/src/usePipeDecoratorRule.ts index d38d16c36..738ccc338 100644 --- a/src/usePipeDecoratorRule.ts +++ b/src/usePipeDecoratorRule.ts @@ -1,58 +1,54 @@ -import * as Lint from 'tslint'; -import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; -import SyntaxKind = require('./util/syntaxKind'); -import { maybeNodeArray } from './util/utils'; +import { IRuleMetadata, RuleFailure, Rules, RuleWalker } from 'tslint/lib'; +import { ClassDeclaration, SourceFile, SyntaxKind } from 'typescript/lib/typescript'; +import { getDecoratorName, getInterfaceName } from './util/utils'; -const getInterfaceName = (t: any) => { - if (t.expression && t.expression.name) { - return t.expression.name.text; - } - return t.expression.text; -}; - -export class Rule extends Lint.Rules.AbstractRule { - public static metadata: Lint.IRuleMetadata = { - ruleName: 'use-pipe-decorator', - type: 'maintainability', +export class Rule extends Rules.AbstractRule { + static readonly metadata: IRuleMetadata = { description: 'Ensure that classes implementing PipeTransform interface, use Pipe decorator.', - rationale: 'Interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.', options: null, optionsDescription: 'Not configurable.', + rationale: 'Interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.', + ruleName: 'use-pipe-decorator', + type: 'maintainability', typescriptOnly: true }; - static FAILURE = 'The %s class implements the PipeTransform interface, so it should use the @Pipe decorator'; - static PIPE_INTERFACE_NAME = 'PipeTransform'; + static readonly FAILURE_STRING = 'The %s class implements the PipeTransform interface, so it should use the @Pipe decorator'; + static readonly PIPE_INTERFACE_NAME = 'PipeTransform'; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + apply(sourceFile: SourceFile): RuleFailure[] { return this.applyWithWalker(new ClassMetadataWalker(sourceFile, this.getOptions())); } } -export class ClassMetadataWalker extends Lint.RuleWalker { - visitClassDeclaration(node: ts.ClassDeclaration) { - if (this.hasIPipeTransform(node)) { - const decorators = maybeNodeArray(node.decorators); - const className: string = node.name.text; - const pipes: Array = decorators - .map(d => (d.expression).text || ((d.expression).expression || {}).text) - .filter(t => t === 'Pipe'); - if (pipes.length === 0) { - this.addFailureAtNode(node, sprintf(Rule.FAILURE, className)); - } - } +const hasPipe = (node: ClassDeclaration): boolean => { + return !!(node.decorators && node.decorators.map(getDecoratorName).some(t => t === 'Pipe')); +}; + +const hasPipeTransform = (node: ClassDeclaration): boolean => { + const { heritageClauses } = node; + + if (!heritageClauses) { + return false; + } + + const interfacesClauses = heritageClauses.filter(h => h.token === SyntaxKind.ImplementsKeyword); + + return interfacesClauses.length > 0 && interfacesClauses[0].types.map(getInterfaceName).indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; +}; + +export class ClassMetadataWalker extends RuleWalker { + visitClassDeclaration(node: ClassDeclaration) { + this.validateClassDeclaration(node); super.visitClassDeclaration(node); } - private hasIPipeTransform(node: ts.ClassDeclaration): boolean { - let interfaces = []; - if (node.heritageClauses) { - let interfacesClause = node.heritageClauses.filter(h => h.token === SyntaxKind.current().ImplementsKeyword); - if (interfacesClause.length !== 0) { - interfaces = interfacesClause[0].types.map(getInterfaceName); - } + private validateClassDeclaration(node: ClassDeclaration) { + if (!hasPipeTransform(node) || hasPipe(node)) { + return; } - return interfaces.indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; + + this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name!.text)); } } diff --git a/src/usePipeTransformInterfaceRule.ts b/src/usePipeTransformInterfaceRule.ts index 75431ec5a..3fbfb4dbc 100644 --- a/src/usePipeTransformInterfaceRule.ts +++ b/src/usePipeTransformInterfaceRule.ts @@ -1,59 +1,54 @@ -import * as Lint from 'tslint'; -import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; -import SyntaxKind = require('./util/syntaxKind'); +import { IRuleMetadata, RuleFailure, Rules, RuleWalker } from 'tslint/lib'; +import { ClassDeclaration, SourceFile, SyntaxKind } from 'typescript/lib/typescript'; +import { getDecoratorName, getInterfaceName } from './util/utils'; -const getInterfaceName = (t: any) => { - if (t.expression && t.expression.name) { - return t.expression.name.text; - } - return t.expression.text; -}; - -export class Rule extends Lint.Rules.AbstractRule { - public static metadata: Lint.IRuleMetadata = { - ruleName: 'use-pipe-transform-interface', - type: 'maintainability', +export class Rule extends Rules.AbstractRule { + static readonly metadata: IRuleMetadata = { description: 'Ensure that pipes implement PipeTransform interface.', - rationale: 'Interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.', options: null, optionsDescription: 'Not configurable.', + rationale: 'Interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.', + ruleName: 'use-pipe-transform-interface', + type: 'maintainability', typescriptOnly: true }; - static FAILURE: string = 'The %s class has the Pipe decorator, so it should implement the PipeTransform interface'; - static PIPE_INTERFACE_NAME = 'PipeTransform'; + static readonly FAILURE_STRING = 'The %s class has the Pipe decorator, so it should implement the PipeTransform interface'; + static readonly PIPE_INTERFACE_NAME = 'PipeTransform'; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + apply(sourceFile: SourceFile): RuleFailure[] { return this.applyWithWalker(new ClassMetadataWalker(sourceFile, this.getOptions())); } } -export class ClassMetadataWalker extends Lint.RuleWalker { - visitClassDeclaration(node: ts.ClassDeclaration) { - let decorators = node.decorators; - if (decorators) { - let pipes: Array = decorators - .map(d => (d.expression).text || ((d.expression).expression || {}).text) - .filter(t => t === 'Pipe'); - if (pipes.length !== 0) { - let className: string = node.name.text; - if (!this.hasIPipeTransform(node)) { - this.addFailureAtNode(node, sprintf(Rule.FAILURE, className)); - } - } - } +const hasPipe = (node: ClassDeclaration): boolean => { + return !!(node.decorators && node.decorators.map(getDecoratorName).some(t => t === 'Pipe')); +}; + +const hasPipeTransform = (node: ClassDeclaration): boolean => { + const { heritageClauses } = node; + + if (!heritageClauses) { + return false; + } + + const interfacesClauses = heritageClauses.filter(h => h.token === SyntaxKind.ImplementsKeyword); + + return interfacesClauses.length > 0 && interfacesClauses[0].types.map(getInterfaceName).indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; +}; + +export class ClassMetadataWalker extends RuleWalker { + visitClassDeclaration(node: ClassDeclaration) { + this.validateClassDeclaration(node); super.visitClassDeclaration(node); } - private hasIPipeTransform(node: ts.ClassDeclaration): boolean { - let interfaces = []; - if (node.heritageClauses) { - let interfacesClause = node.heritageClauses.filter(h => h.token === SyntaxKind.current().ImplementsKeyword); - if (interfacesClause.length !== 0) { - interfaces = interfacesClause[0].types.map(getInterfaceName); - } + private validateClassDeclaration(node: ClassDeclaration) { + if (!hasPipe(node) || hasPipeTransform(node)) { + return; } - return interfaces.indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; + + this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name!.text)); } } diff --git a/src/util/astQuery.ts b/src/util/astQuery.ts index 09f9e49a6..64e469ac3 100644 --- a/src/util/astQuery.ts +++ b/src/util/astQuery.ts @@ -1,76 +1,57 @@ import * as ts from 'typescript'; -import { current } from './syntaxKind'; import { Maybe, ifTrue } from './function'; -const kinds = current(); -export function isCallExpression(expr: ts.LeftHandSideExpression): expr is ts.CallExpression { - return expr && expr.kind === kinds.CallExpression; -} - -export function callExpression(dec: ts.Decorator): Maybe { - return Maybe.lift(dec.expression).fmap(expr => (isCallExpression(expr) ? (expr as ts.CallExpression) : undefined)); +export function callExpression(dec?: ts.Decorator): Maybe { + return Maybe.lift(dec!.expression).fmap(expr => (ts.isCallExpression(expr!) ? (expr as ts.CallExpression) : undefined)); } export function isPropertyAssignment(expr: ts.ObjectLiteralElement): expr is ts.PropertyAssignment { - return expr && expr.kind === kinds.PropertyAssignment; + return expr && expr.kind === ts.SyntaxKind.PropertyAssignment; } export function isSimpleTemplateString(expr: ts.Expression): expr is ts.StringLiteral | ts.NoSubstitutionTemplateLiteral { - return (expr && expr.kind === kinds.StringLiteral) || expr.kind === kinds.NoSubstitutionTemplateLiteral; -} - -export function isArrayLiteralExpression(expr: ts.Expression): expr is ts.ArrayLiteralExpression { - return expr && expr.kind === kinds.ArrayLiteralExpression; -} - -export function hasProperties(expr: ts.ObjectLiteralExpression): boolean { - return expr && !!expr.properties; + return (expr && expr.kind === ts.SyntaxKind.StringLiteral) || expr.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral; } -export function isObjectLiteralExpression(expr: ts.Expression): expr is ts.ObjectLiteralExpression { - return expr && expr.kind === kinds.ObjectLiteralExpression; +export function hasProperties(expr?: ts.ObjectLiteralExpression): boolean { + return !!(expr && expr.properties); } -export function objectLiteralExpression(expr: ts.CallExpression): Maybe { - return Maybe.lift(expr.arguments[0]).fmap(arg0 => (isObjectLiteralExpression(arg0) ? (arg0 as ts.ObjectLiteralExpression) : undefined)); -} - -export function isIdentifier(expr: ts.PropertyName | ts.LeftHandSideExpression): expr is ts.Identifier { - return expr && expr.kind === kinds.Identifier; +export function objectLiteralExpression(expr?: ts.CallExpression): Maybe { + return Maybe.lift(expr!.arguments[0]).fmap( + arg0 => (ts.isObjectLiteralExpression(arg0!) ? (arg0 as ts.ObjectLiteralExpression) : undefined) + ); } -export function withIdentifier(identifier: string): (expr: ts.CallExpression) => Maybe { - return ifTrue((expr: ts.CallExpression) => isIdentifier(expr.expression) && expr.expression.text === identifier); +export function withIdentifier(identifier: string): (expr: ts.CallExpression) => Maybe { + return ifTrue(expr => ts.isIdentifier(expr.expression) && expr.expression.text === identifier); } export type WithStringInitializer = ts.StringLiteral | ts.NoSubstitutionTemplateLiteral; export function isProperty(propName: string, p: ts.ObjectLiteralElement): boolean { - return isPropertyAssignment(p) && isIdentifier(p.name) && p.name.text === propName; + return isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === propName; } -export function getInitializer(p: ts.ObjectLiteralElement): Maybe { - return Maybe.lift(isPropertyAssignment(p) && isIdentifier(p.name) ? p.initializer : undefined); +export function getInitializer(p: ts.ObjectLiteralElement): Maybe { + return Maybe.lift(isPropertyAssignment(p) && ts.isIdentifier(p.name) ? p.initializer : undefined); } export function getStringInitializerFromProperty( propertyName: string, ps: ts.NodeArray -): Maybe { - const property = ps.find(p => isProperty(propertyName, p)); +): Maybe { + const property = ps.find(p => isProperty(propertyName, p))!; + return ( getInitializer(property) // A little wrinkle to return Maybe - .fmap(expr => (isSimpleTemplateString(expr) ? (expr as WithStringInitializer) : undefined)) + .fmap(expr => (isSimpleTemplateString(expr!) ? (expr as WithStringInitializer) : undefined)) ); } -export function decoratorArgument(dec: ts.Decorator): Maybe { +export function decoratorArgument(dec: ts.Decorator): Maybe { return Maybe.lift(dec) .bind(callExpression) .bind(objectLiteralExpression); } - -export function isDecorator(expr: ts.Node): expr is ts.Decorator { - return expr && expr.kind === kinds.Decorator; -} diff --git a/src/util/classDeclarationUtils.ts b/src/util/classDeclarationUtils.ts index a6396b9a7..732c6af91 100644 --- a/src/util/classDeclarationUtils.ts +++ b/src/util/classDeclarationUtils.ts @@ -1,18 +1,18 @@ import * as ts from 'typescript'; -import { current } from './syntaxKind'; import { FlatSymbolTable } from '../angular/templates/recursiveAngularExpressionVisitor'; -const SyntaxKind = current(); - export const getDeclaredProperties = (declaration: ts.ClassDeclaration) => { const m = declaration.members; - const ctr = m.filter((m: any) => m.kind === SyntaxKind.Constructor).pop(); + const ctr = m.filter((m: any) => m.kind === ts.SyntaxKind.Constructor).pop(); let params: any = []; if (ctr) { - params = (((ctr).parameters || []) as any).filter((p: any) => p.kind === SyntaxKind.Parameter); + params = (((ctr).parameters || []) as any).filter((p: any) => p.kind === ts.SyntaxKind.Parameter); } return m - .filter((m: any) => m.kind === SyntaxKind.PropertyDeclaration || m.kind === SyntaxKind.GetAccessor || m.kind === SyntaxKind.SetAccessor) + .filter( + (m: any) => + m.kind === ts.SyntaxKind.PropertyDeclaration || m.kind === ts.SyntaxKind.GetAccessor || m.kind === ts.SyntaxKind.SetAccessor + ) .concat(params); }; @@ -26,13 +26,13 @@ export const getDeclaredPropertyNames = (declaration: ts.ClassDeclaration) => { }; export const getDeclaredMethods = (declaration: ts.ClassDeclaration) => { - return declaration.members.filter((m: any) => m.kind === SyntaxKind.MethodDeclaration); + return declaration.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration); }; export const getDeclaredMethodNames = (declaration: ts.ClassDeclaration) => { return getDeclaredMethods(declaration) - .map((d: any) => (d.name).text) - .reduce((accum: FlatSymbolTable, m: string) => { + .map(d => (d.name).text) + .reduce((accum, m) => { accum[m] = true; return accum; }, {}); diff --git a/src/util/function.ts b/src/util/function.ts index fcb4c8d21..44c38ba11 100644 --- a/src/util/function.ts +++ b/src/util/function.ts @@ -11,35 +11,29 @@ export interface F3 { (a0: A0, a1: A1, a2: A2): R; } -function nullOrUndef(t: any) { +function nullOrUndef(t: any): t is null | undefined { return t === null || t === undefined; } export class Maybe { - static nothing = new Maybe(undefined); + static nothing = new Maybe(undefined); - static lift(t: T) { - if (nullOrUndef(t)) { - return Maybe.nothing; - } - return new Maybe(t); + static lift(t: T | undefined): Maybe { + return new Maybe(nullOrUndef(t) ? undefined : t); } - static all(t0: Maybe, t1: Maybe): Maybe<[T0, T1]> { + static all(t0: Maybe, t1: Maybe): Maybe<[T0, T1] | undefined> { return t0.bind(_t0 => t1.fmap(_t1 => [_t0, _t1] as [T0, T1])); } private constructor(private t: T | undefined) {} - bind(fn: F1>): Maybe { - if (!nullOrUndef(this.t)) { - return fn(this.t); - } - return Maybe.nothing; + bind(fn: F1>): Maybe { + return nullOrUndef(this.t) ? Maybe.nothing : fn(this.t); } - fmap(fn: F1): Maybe { - return this.bind(t => Maybe.lift(fn(t))); + fmap(fn: F1): Maybe { + return this.bind(t => Maybe.lift(fn(t!))); } get isNothing() { @@ -54,6 +48,7 @@ export class Maybe { if (this.isNothing) { return def(); } + return this; } @@ -71,17 +66,18 @@ export function unwrapFirst(ts: Maybe[]): T | undefined { } export function all(...preds: F1[]): F1 { - return (t: T) => !preds.find(p => !p(t)); + return (t: T | undefined) => !preds.find(p => !p(t!)); } export function any(...preds: F1[]): F1 { - return (t: T) => !!preds.find(p => p(t)); + return (t: T | undefined) => !!preds.find(p => p(t!)); } -export function ifTrue(pred: F1): F1> { - return (t: T) => (pred(t) ? Maybe.lift(t) : Maybe.nothing); +export function ifTrue(pred: F1): F1> { + return (t: T | undefined) => (pred(t!) ? Maybe.lift(t) : Maybe.nothing); } -export function listToMaybe(ms: Maybe[]): Maybe { - const unWrapped = ms.filter(m => m.isSomething).map(m => m.unwrap()); - return unWrapped.length !== 0 ? Maybe.lift(unWrapped) : Maybe.nothing; +export function listToMaybe(ms?: Maybe[]): Maybe<(T | undefined)[]> | Maybe { + const unWrapped = (ms || []).filter(m => m.isSomething).map(m => m.unwrap()); + + return unWrapped.length === 0 ? Maybe.nothing : Maybe.lift(unWrapped); } diff --git a/src/util/ngQuery.ts b/src/util/ngQuery.ts index 2abc35141..06330bdc5 100644 --- a/src/util/ngQuery.ts +++ b/src/util/ngQuery.ts @@ -1,27 +1,19 @@ import * as ts from 'typescript'; +import { decoratorArgument, getInitializer, getStringInitializerFromProperty, isProperty, WithStringInitializer } from './astQuery'; import { Maybe } from './function'; -import { - decoratorArgument, - getInitializer, - isProperty, - isArrayLiteralExpression, - WithStringInitializer, - getStringInitializerFromProperty -} from './astQuery'; -export function getInlineStyle(dec: ts.Decorator): Maybe { - return decoratorArgument(dec).bind((expr: ts.ObjectLiteralExpression) => { - const property = expr.properties.find(p => isProperty('styles', p)); - return getInitializer(property).fmap(expr => (isArrayLiteralExpression(expr) ? (expr as ts.ArrayLiteralExpression) : undefined)); +export function getInlineStyle(dec: ts.Decorator): Maybe { + return decoratorArgument(dec).bind(expr => { + const property = expr!.properties.find(p => isProperty('styles', p))!; + + return getInitializer(property).fmap(expr => (ts.isArrayLiteralExpression(expr!) ? (expr as ts.ArrayLiteralExpression) : undefined)); }); } -export function getTemplateUrl(dec: ts.Decorator): Maybe { - return decoratorArgument(dec).bind((expr: ts.ObjectLiteralExpression) => - getStringInitializerFromProperty('templateUrl', expr.properties) - ); +export function getTemplate(dec: ts.Decorator): Maybe { + return decoratorArgument(dec).bind(expr => getStringInitializerFromProperty('template', expr!.properties)); } -export function getTemplate(dec: ts.Decorator): Maybe { - return decoratorArgument(dec).bind((expr: ts.ObjectLiteralExpression) => getStringInitializerFromProperty('template', expr.properties)); +export function getTemplateUrl(dec: ts.Decorator): Maybe { + return decoratorArgument(dec).bind(expr => getStringInitializerFromProperty('templateUrl', expr!.properties)); } diff --git a/src/util/selectorValidator.ts b/src/util/selectorValidator.ts index da99cdbf1..ef01d0ccc 100644 --- a/src/util/selectorValidator.ts +++ b/src/util/selectorValidator.ts @@ -17,22 +17,25 @@ export const SelectorValidator = { prefix(prefix: string, selectorType: string): Function { const regex = new RegExp(`^\\[?(${prefix})`); + return (selector: string) => { if (!prefix) { return true; } + if (!regex.test(selector)) { return false; - } else { - const suffix = selector.replace(regex, ''); - if (selectorType === 'camelCase') { - return !suffix || suffix[0] === suffix[0].toUpperCase(); - } else if (selectorType === 'kebab-case') { - return !suffix || suffix[0] === '-'; - } else { - throw new Error('Invalid selector type'); - } } + + const suffix = selector.replace(regex, ''); + + if (selectorType === 'camelCase') { + return !suffix || suffix[0] === suffix[0].toUpperCase(); + } else if (selectorType === 'kebab-case') { + return !suffix || suffix[0] === '-'; + } + + throw Error('Invalid selector type'); }; } }; diff --git a/src/util/utils.ts b/src/util/utils.ts index 4bf234565..a5b148a04 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -2,11 +2,11 @@ import * as ts from 'typescript'; const SyntaxKind = require('./syntaxKind'); // Lewenshtein algorithm -export const stringDistance = (s: string, t: string, ls: number = s.length, lt: number = t.length) => { - let memo = []; +export const stringDistance = (s: string, t: string, ls = s.length, lt = t.length) => { + let memo = {}; let currRowMemo; - let i; - let k; + let i: number; + let k: number; for (k = 0; k <= lt; k += 1) { memo[k] = k; } @@ -25,27 +25,24 @@ export const isSimpleTemplateString = (e: any) => { }; export const getDecoratorPropertyInitializer = (decorator: ts.Decorator, name: string) => { - return ((decorator.expression).arguments[0]).properties - .map((prop: any) => { - if (prop.name.text === name) { - return prop; - } - return null; - }) - .filter((el: any) => !!el) - .map((prop: any) => prop.initializer) - .pop(); + return ( + ((decorator.expression).arguments[0]).properties + // .map(prop => ((prop.name as any).text === name) ? prop : null) + .filter(prop => (prop.name as any).text === name) + .map((prop: any) => prop.initializer) + .pop() + ); }; -export const getDecoratorName = (decorator: ts.Decorator) => { - let baseExpr = decorator.expression || {}; - let expr = baseExpr.expression || {}; - return expr.text; +export const getDecoratorName = (decorator: ts.Decorator): string | undefined => { + return ts.isCallExpression(decorator.expression) && ts.isIdentifier(decorator.expression.expression) + ? decorator.expression.expression.text + : undefined; }; export const getComponentDecorator = (declaration: ts.ClassDeclaration) => { return ([].slice.apply(declaration.decorators) || []) - .filter((d: any) => { + .filter(d => { if ( !(d.expression).arguments || !(d.expression).arguments.length || @@ -61,6 +58,12 @@ export const getComponentDecorator = (declaration: ts.ClassDeclaration) => { .pop(); }; +export const getInterfaceName = (expression: ts.ExpressionWithTypeArguments): string => { + const { expression: childExpression } = expression; + + return ts.isPropertyAccessExpression(childExpression) ? childExpression.name.getText() : childExpression.getText(); +}; + export const maybeNodeArray = (nodes: ts.NodeArray): ReadonlyArray => { if (!nodes) { return []; diff --git a/src/walkerFactory/walkerFactory.ts b/src/walkerFactory/walkerFactory.ts index f81065163..fe133f25d 100644 --- a/src/walkerFactory/walkerFactory.ts +++ b/src/walkerFactory/walkerFactory.ts @@ -21,7 +21,7 @@ export interface WalkerBuilder { } class NgComponentWalkerBuilder implements WalkerBuilder<'NgComponent'> { - private _where: F1>; + private _where!: F1>; where(validate: F1>): NgComponentWalkerBuilder { this._where = validate; @@ -33,7 +33,7 @@ class NgComponentWalkerBuilder implements WalkerBuilder<'NgComponent'> { const e = class extends NgWalker { visitNgComponent(meta: ComponentMetadata) { self._where(meta).fmap(failure => { - this.addFailureAtNode(failure.node, failure.message); + this.addFailureAtNode(failure!.node, failure!.message); }); super.visitNgComponent(meta); } diff --git a/src/walkerFactory/walkerFn.ts b/src/walkerFactory/walkerFn.ts index 5687e2166..0fd0dc527 100644 --- a/src/walkerFactory/walkerFn.ts +++ b/src/walkerFactory/walkerFn.ts @@ -6,7 +6,7 @@ import { F1, F2, Maybe } from '../util/function'; import { Failure } from './walkerFactory'; export type Validator = NodeValidator | ComponentValidator; -export type ValidateFn = F2>; +export type ValidateFn = F2>; export type WalkerOptions = any; export interface NodeValidator { @@ -22,11 +22,11 @@ export interface ComponentValidator { export function validate(syntaxKind: ts.SyntaxKind): F1, NodeValidator> { return validateFn => ({ kind: 'Node', - validate: (node: ts.Node, options: WalkerOptions) => (node.kind === syntaxKind ? validateFn(node, options) : Maybe.nothing) + validate: (node: ts.Node, options: WalkerOptions) => (node.kind === syntaxKind ? validateFn!(node, options) : Maybe.nothing) }); } -export function validateComponent(validate: F2>): ComponentValidator { +export function validateComponent(validate: F2>): ComponentValidator { return { kind: 'NgComponent', validate @@ -39,7 +39,7 @@ export function all(...validators: Validator[]): F2 { if (v.kind === 'NgComponent') { - v.validate(meta, this.getOptions()).fmap(failures => failures.forEach(f => this.generateFailure(f))); + v.validate(meta, this.getOptions()).fmap(failures => failures!.forEach(f => this.generateFailure(f))); } }); super.visitNgComponent(meta); @@ -48,7 +48,7 @@ export function all(...validators: Validator[]): F2 { if (v.kind === 'Node') { - v.validate(node, this.getOptions()).fmap(failures => failures.forEach(f => this.generateFailure(f))); + v.validate(node, this.getOptions()).fmap(failures => failures!.forEach(f => this.generateFailure(f))); } }); super.visitNode(node); diff --git a/test/angular/basicCssAstVisitor.spec.ts b/test/angular/basicCssAstVisitor.spec.ts index 7765e1c29..5f6613aeb 100644 --- a/test/angular/basicCssAstVisitor.spec.ts +++ b/test/angular/basicCssAstVisitor.spec.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import * as tslint from 'tslint'; +import * as Lint from 'tslint'; import { NgWalker } from '../../src/angular/ngWalker'; import { BasicCssAstVisitor } from '../../src/angular/styles/basicCssAstVisitor'; @@ -20,13 +20,13 @@ describe('basicCssAstVisitor', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -49,13 +49,13 @@ describe('basicCssAstVisitor', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { diff --git a/test/angular/metadataReader.spec.ts b/test/angular/metadataReader.spec.ts index da1b0e663..6ff5b2e6d 100644 --- a/test/angular/metadataReader.spec.ts +++ b/test/angular/metadataReader.spec.ts @@ -9,7 +9,7 @@ import { Config } from '../../src/angular/config'; import { join, normalize } from 'path'; -const getAst = (code: string, file = 'file.ts') => ts.createSourceFile(file, code, ts.ScriptTarget.ES2015, true); +const getAst = (code: string, file = 'file.ts') => ts.createSourceFile(file, code, ts.ScriptTarget.ES5, true); const last = (nodes: ts.NodeArray) => nodes[nodes.length - 1]; @@ -24,7 +24,7 @@ describe('metadataReader', () => { `; const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); - const metadata = reader.read(last(ast.statements)); + const metadata = reader.read(last(ast.statements))!; chai.expect(metadata.selector).eq('foo'); }); @@ -35,7 +35,7 @@ describe('metadataReader', () => { `; const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); - const metadata = reader.read(last(ast.statements)); + const metadata = reader.read(last(ast.statements))!; chai.expect(metadata.selector).eq(undefined); }); @@ -47,7 +47,7 @@ describe('metadataReader', () => { const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata.controller).eq(classDeclaration); }); }); @@ -65,14 +65,14 @@ describe('metadataReader', () => { const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('bar'); - chai.expect(m.template.url).eq(null); + chai.expect(m.template.url).eq(undefined); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); }); it('should work with external template', () => { @@ -87,14 +87,14 @@ describe('metadataReader', () => { const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq(''); chai.expect(m.template.url).eq('bar'); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); }); it('should work with ignore templateUrl when has template', () => { @@ -110,14 +110,14 @@ describe('metadataReader', () => { const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('qux'); - chai.expect(m.template.url).eq(null); + chai.expect(m.template.url).eq(undefined); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); }); it('should work with relative paths', () => { @@ -132,14 +132,14 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq('
'); - chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.template.url!.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); }); it('should work with absolute paths', () => { @@ -154,23 +154,23 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq('
'); - chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.template.url!.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); }); it('should work invoke Config.resolveUrl after all resolves', () => { let invoked = false; const bak = Config.resolveUrl; try { - Config.resolveUrl = (url: string) => { + Config.resolveUrl = url => { invoked = true; - chai.expect(url.startsWith(normalize(join(__dirname, '../..')))).eq(true); + chai.expect(url!.startsWith(normalize(join(__dirname, '../..')))).eq(true); return url; }; const code = ` @@ -186,14 +186,14 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); chai.expect(invoked).eq(false); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq('
'); - chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.template.url!.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); chai.expect(invoked).eq(true); } finally { Config.resolveUrl = bak; @@ -204,7 +204,7 @@ describe('metadataReader', () => { let invoked = false; const bak = Config.transformTemplate; try { - Config.transformTemplate = (code: string) => { + Config.transformTemplate = code => { invoked = true; chai.expect(code.trim()).eq('
'); return { code }; @@ -222,14 +222,14 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); chai.expect(invoked).eq(false); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq('
'); - chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.template.url!.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); chai.expect(invoked).eq(true); } finally { Config.transformTemplate = bak; @@ -240,7 +240,7 @@ describe('metadataReader', () => { let invoked = false; const bak = Config.transformStyle; try { - Config.transformStyle = (code: string) => { + Config.transformStyle = code => { invoked = true; chai.expect(code).eq('baz'); return { code }; @@ -258,14 +258,14 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); chai.expect(invoked).eq(false); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq('
'); - chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.template.url!.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); chai.expect(invoked).eq(true); } finally { Config.transformStyle = bak; @@ -285,14 +285,14 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/specialsymbols/foo.ts'); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq('
`
'); - chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.template.url!.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(null); + chai.expect(m.styles[0].url).eq(undefined); }); it('should work work with templates with "`"', () => { @@ -308,14 +308,14 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/notsupported/foo.ts'); const classDeclaration = last(ast.statements); - const metadata = reader.read(classDeclaration); + const metadata = reader.read(classDeclaration)!; chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code.trim()).eq(''); - chai.expect(m.template.url.endsWith('foo.dust')).eq(true); + chai.expect(m.template.url!.endsWith('foo.dust')).eq(true); chai.expect(m.styles[0].style.code).eq(''); - chai.expect(m.styles[0].url.endsWith('foo.sty')).eq(true); + chai.expect(m.styles[0].url!.endsWith('foo.sty')).eq(true); }); }); }); diff --git a/test/angular/ngWalker.spec.ts b/test/angular/ngWalker.spec.ts index ab6cfbff9..2d5c62850 100644 --- a/test/angular/ngWalker.spec.ts +++ b/test/angular/ngWalker.spec.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import * as tslint from 'tslint'; +import * as Lint from 'tslint'; import { NgWalker } from '../../src/angular/ngWalker'; import { RecursiveAngularExpressionVisitor } from '../../src/angular/templates/recursiveAngularExpressionVisitor'; @@ -32,13 +32,13 @@ describe('ngWalker', () => { @Injectable() class FooService {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); let cmpSpy = chaiSpy.on(walker, 'visitNgComponent'); let dirSpy = chaiSpy.on(walker, 'visitNgDirective'); @@ -65,19 +65,19 @@ describe('ngWalker', () => { foobar; } `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); let outputsSpy = chaiSpy.on(walker, 'visitNgOutput'); let inputsSpy = chaiSpy.on(walker, 'visitNgInput'); walker.walk(sf); - (chai.expect(outputsSpy).to.have.been).called(); - (chai.expect(inputsSpy).to.have.been).called(); + (chai.expect(outputsSpy).to.have.been as any).called(); + (chai.expect(inputsSpy).to.have.been as any).called(); }); it('should visit component templates', () => { @@ -88,13 +88,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs, { templateVisitorCtrl: BasicTemplateAstVisitor }); @@ -111,13 +111,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); @@ -133,13 +133,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -159,13 +159,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -185,13 +185,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -210,13 +210,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -232,13 +232,13 @@ describe('ngWalker', () => { @Component() class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -254,13 +254,13 @@ describe('ngWalker', () => { @Component class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -279,13 +279,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -303,13 +303,13 @@ describe('ngWalker', () => { }) class Baz {} `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { @@ -349,13 +349,13 @@ describe('ngWalker', () => { } } `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); (chai) .expect(() => { diff --git a/test/angular/sourceMappingVisitor.spec.ts b/test/angular/sourceMappingVisitor.spec.ts index 0992b2ddb..9d91046e5 100644 --- a/test/angular/sourceMappingVisitor.spec.ts +++ b/test/angular/sourceMappingVisitor.spec.ts @@ -6,7 +6,7 @@ import { SourceMappingVisitor } from '../../src/angular/sourceMappingVisitor'; import { getDecoratorPropertyInitializer } from '../../src/util/utils'; const getAst = (code: string, file = 'file.ts') => { - return ts.createSourceFile(file, code, ts.ScriptTarget.ES2015, true); + return ts.createSourceFile(file, code, ts.ScriptTarget.ES5, true); }; const fixture1 = `@Component({ @@ -29,14 +29,14 @@ describe('SourceMappingVisitor', () => { it('should map to correct position', () => { const ast = getAst(fixture1); const classDeclaration = last(ast.statements); - const styles = getDecoratorPropertyInitializer(last(classDeclaration.decorators), 'styles'); + const styles = getDecoratorPropertyInitializer(last(classDeclaration.decorators!), 'styles'); const styleNode = styles.elements[0]; const scss = (styleNode).text; const result = renderSync({ outFile: '/tmp/bar', data: scss, sourceMap: true }); const visitor = new SourceMappingVisitor( ast, { - disabledIntervals: null, + disabledIntervals: [], ruleArguments: [], ruleName: 'foo', ruleSeverity: 'warning' diff --git a/test/angular/urlResolvers/urlResolver.spec.ts b/test/angular/urlResolvers/urlResolver.spec.ts index aee5605bd..1ed9b9d30 100644 --- a/test/angular/urlResolvers/urlResolver.spec.ts +++ b/test/angular/urlResolvers/urlResolver.spec.ts @@ -4,7 +4,7 @@ import chai = require('chai'); import { AbstractResolver } from '../../../src/angular/urlResolvers/abstractResolver'; const getAst = (code: string) => { - return ts.createSourceFile('file.ts', code, ts.ScriptTarget.ES2015, true); + return ts.createSourceFile('file.ts', code, ts.ScriptTarget.ES5, true); }; class DummyResolver extends AbstractResolver { @@ -34,7 +34,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const template = resolver.getTemplate(last(ast.statements).decorators[0]); + const template = resolver.getTemplate(last(ast.statements).decorators![0]); (chai).expect(template).eq('./foo/bar'); }); @@ -47,7 +47,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const template = resolver.getTemplate(last(ast.statements).decorators[0]); + const template = resolver.getTemplate(last(ast.statements).decorators![0]); (chai).expect(template).eq('./foo/bar'); }); @@ -60,8 +60,8 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const template = resolver.getTemplate(last(ast.statements).decorators[0]); - (chai).expect(template).eq(null); + const template = resolver.getTemplate(last(ast.statements).decorators![0]); + (chai).expect(template).eq(undefined); }); it('should not be able to resolve missing templateUrl', () => { @@ -73,8 +73,8 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const template = resolver.getTemplate(last(ast.statements).decorators[0]); - (chai).expect(template).eq(null); + const template = resolver.getTemplate(last(ast.statements).decorators![0]); + (chai).expect(template).eq(undefined); }); it('should not be able to resolve templateUrls when having missing object literal', () => { @@ -84,7 +84,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const template = resolver.getTemplate(last(ast.statements).decorators[0]); + const template = resolver.getTemplate(last(ast.statements).decorators![0]); (chai).expect(template).eq(null); }); @@ -95,7 +95,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const template = resolver.getTemplate(last(ast.statements).decorators[0]); + const template = resolver.getTemplate(last(ast.statements).decorators![0]); (chai).expect(template).eq(null); }); }); @@ -108,7 +108,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const styles = resolver.getStyles(last(ast.statements).decorators[0]); + const styles = resolver.getStyles(last(ast.statements).decorators![0]); chai.expect(styles).to.deep.equal([]); }); @@ -119,7 +119,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const styles = resolver.getStyles(last(ast.statements).decorators[0]); + const styles = resolver.getStyles(last(ast.statements).decorators![0]); chai.expect(styles).to.deep.equal([]); }); @@ -135,7 +135,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const styles = resolver.getStyles(last(ast.statements).decorators[0]); + const styles = resolver.getStyles(last(ast.statements).decorators![0]); chai.expect(styles).to.deep.equal(['./foo', './bar']); }); @@ -151,7 +151,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const styles = resolver.getStyles(last(ast.statements).decorators[0]); + const styles = resolver.getStyles(last(ast.statements).decorators![0]); chai.expect(styles).to.deep.equal(['./foo', './bar']); }); @@ -169,7 +169,7 @@ describe('urlResolver', () => { `; const ast = getAst(source); const resolver = new DummyResolver(); - const styles = resolver.getStyles(last(ast.statements).decorators[0]); + const styles = resolver.getStyles(last(ast.statements).decorators![0]); chai.expect(styles).to.deep.equal(['./foo', './bar']); }); }); diff --git a/test/angularWhitespaceRule.spec.ts b/test/angularWhitespaceRule.spec.ts index e4d39c696..41eb7102c 100644 --- a/test/angularWhitespaceRule.spec.ts +++ b/test/angularWhitespaceRule.spec.ts @@ -1,10 +1,7 @@ import { assertSuccess, assertAnnotated, assertMultipleAnnotated } from './testHelper'; import { Replacement } from 'tslint'; import { expect } from 'chai'; -import { FsFileResolver } from '../src/angular/fileResolver/fsFileResolver'; -import { MetadataReader } from '../src/angular/metadataReader'; import * as ts from 'typescript'; -import chai = require('chai'); const getAst = (code: string, file = 'file.ts') => { return ts.createSourceFile(file, code, ts.ScriptTarget.ES5, true); @@ -117,7 +114,6 @@ describe('angular-whitespace', () => { }) class Bar {} `; - const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/angularWhitespace/component.ts'); assertSuccess('angular-whitespace', ast, ['check-pipe']); }); @@ -166,7 +162,6 @@ describe('angular-whitespace', () => { ponies = [] } `; - const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/angularWhitespace/component.ts'); assertSuccess('angular-whitespace', ast, ['check-pipe']); }); diff --git a/test/contextualLifeCycleRule.spec.ts b/test/contextualLifeCycleRule.spec.ts index 9e5c49181..4597da196 100644 --- a/test/contextualLifeCycleRule.spec.ts +++ b/test/contextualLifeCycleRule.spec.ts @@ -1,4 +1,4 @@ -import { assertAnnotated, assertFailures, assertSuccess } from './testHelper'; +import { assertAnnotated, assertSuccess } from './testHelper'; describe('contextual-life-cycle', () => { describe('success', () => { diff --git a/test/i18nRule.spec.ts b/test/i18nRule.spec.ts index 3d5326d2c..dde565f13 100644 --- a/test/i18nRule.spec.ts +++ b/test/i18nRule.spec.ts @@ -1,15 +1,5 @@ -import { assertSuccess, assertAnnotated, assertMultipleAnnotated } from './testHelper'; -import { Replacement } from 'tslint'; -import { expect } from 'chai'; -import { FsFileResolver } from '../src/angular/fileResolver/fsFileResolver'; -import { MetadataReader } from '../src/angular/metadataReader'; -import * as ts from 'typescript'; +import { assertSuccess, assertAnnotated } from './testHelper'; import { assertFailure } from './testHelper'; -import chai = require('chai'); - -const getAst = (code: string, file = 'file.ts') => { - return ts.createSourceFile(file, code, ts.ScriptTarget.ES5, true); -}; describe('i18n', () => { describe('check-id', () => { diff --git a/test/importDestructuringSpacingRule.spec.ts b/test/importDestructuringSpacingRule.spec.ts index 5a77d41f6..8921c5dba 100644 --- a/test/importDestructuringSpacingRule.spec.ts +++ b/test/importDestructuringSpacingRule.spec.ts @@ -66,7 +66,7 @@ describe(ruleName, () => { return; } - const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix())); + const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix()!)); expect(replacement).to.eq(` import { Foo } from './foo'; @@ -85,7 +85,7 @@ describe(ruleName, () => { return; } - const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix())); + const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix()!)); expect(replacement).to.eq(` import { Bar, BarFoo, Foo } from './foo'; @@ -104,7 +104,7 @@ describe(ruleName, () => { return; } - const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix())); + const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix()!)); expect(replacement).to.eq(` import { Bar, BarFoo, Foo } from './foo'; @@ -123,7 +123,7 @@ describe(ruleName, () => { return; } - const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix())); + const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix()!)); expect(replacement).to.eq(` import { Bar, BarFoo, Foo } from './foo'; diff --git a/test/maxInlineDeclarationsRule.spec.ts b/test/maxInlineDeclarationsRule.spec.ts index 7f12db88e..8126a4bde 100644 --- a/test/maxInlineDeclarationsRule.spec.ts +++ b/test/maxInlineDeclarationsRule.spec.ts @@ -10,7 +10,7 @@ const { const filePath = `${__dirname}/../../test/fixtures/inlineTemplateMaxLines/foo.ts`; const getSourceFile = (code: string): SourceFile => { - return createSourceFile(filePath, code, ScriptTarget.ES5, true); + return createSourceFile(filePath, code, ScriptTarget.ES2015, true); }; describe(ruleName, () => { diff --git a/test/noInputPrefixRule.spec.ts b/test/noInputPrefixRule.spec.ts index 8ad4f241c..77ff87d70 100644 --- a/test/noInputPrefixRule.spec.ts +++ b/test/noInputPrefixRule.spec.ts @@ -2,7 +2,6 @@ import { getFailureMessage, Rule } from '../src/noInputPrefixRule'; import { assertAnnotated, assertSuccess } from './testHelper'; const { - FAILURE_STRING, metadata: { ruleName } } = Rule; diff --git a/test/noTemplateCallExpressionRule.spec.ts b/test/noTemplateCallExpressionRule.spec.ts index e652c7d48..d6cbb2e4c 100644 --- a/test/noTemplateCallExpressionRule.spec.ts +++ b/test/noTemplateCallExpressionRule.spec.ts @@ -1,6 +1,4 @@ import { assertSuccess, assertAnnotated } from './testHelper'; -import { Replacement } from 'tslint'; -import { expect } from 'chai'; describe('no-template-call-expression', () => { describe('success', () => { diff --git a/test/noUnusedCssRule.spec.ts b/test/noUnusedCssRule.spec.ts index e3c3413fe..dcb85fe29 100644 --- a/test/noUnusedCssRule.spec.ts +++ b/test/noUnusedCssRule.spec.ts @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import { Decorator } from 'typescript'; import * as sass from 'node-sass'; @@ -770,14 +769,14 @@ describe('no-unused-css', () => { }); it('should work with sass', () => { - Config.transformStyle = (source: string, url: string, d: Decorator) => { + Config.transformStyle = (source: string) => { const res = sass.renderSync({ sourceMap: true, data: source, sourceMapEmbed: true }); const code = res.css.toString(); - const base64Map = code.match(/\/\*(.*?)\*\//)[1].replace('# sourceMappingURL=data:application/json;base64,', ''); + const base64Map = code.match(/\/\*(.*?)\*\//)![1].replace('# sourceMappingURL=data:application/json;base64,', ''); const map = JSON.parse(Buffer.from(base64Map, 'base64').toString('ascii')); return { code, source, map }; }; @@ -809,7 +808,7 @@ describe('no-unused-css', () => { message: 'Unused styles', source }); - Config.transformStyle = (code: string) => ({ code, map: null }); + Config.transformStyle = code => ({ code, map: null }); }); describe('inconsistencies with template', () => { @@ -866,7 +865,7 @@ describe('no-unused-css', () => { } }, null - ); + )!; const replacement = failures[0].getFix() as Replacement; expect(replacement.text).to.eq(''); expect(replacement.start).to.eq(199); @@ -874,14 +873,14 @@ describe('no-unused-css', () => { }); it('should work with SASS', () => { - Config.transformStyle = (source: string, url: string, d: Decorator) => { + Config.transformStyle = source => { const res = sass.renderSync({ sourceMap: true, data: source, sourceMapEmbed: true }); const code = res.css.toString(); - const base64Map = code.match(/\/\*(.*?)\*\//)[1].replace('# sourceMappingURL=data:application/json;base64,', ''); + const base64Map = code.match(/\/\*(.*?)\*\//)![1].replace('# sourceMappingURL=data:application/json;base64,', ''); const map = JSON.parse(Buffer.from(base64Map, 'base64').toString('ascii')); return { code, source, map }; }; @@ -916,7 +915,7 @@ describe('no-unused-css', () => { line: 10, character: 18 } - }); + })!; Config.transformStyle = (code: string) => ({ code, map: null }); const replacement = failures[0].getFix() as Replacement; expect(replacement.text).to.eq(''); diff --git a/test/preferInlineDecoratorRule.spec.ts b/test/preferInlineDecoratorRule.spec.ts index 239db20f3..eb2901321 100644 --- a/test/preferInlineDecoratorRule.spec.ts +++ b/test/preferInlineDecoratorRule.spec.ts @@ -41,8 +41,8 @@ describe(ruleName, () => { childTest: ChildTest; } `; - const failures = assertFailure(ruleName, source, expectedFailure); - const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix())); + const failures = assertFailure(ruleName, source, expectedFailure)!; + const replacement = Replacement.applyFixes(source, failures.map(f => f.getFix()!)); expect(replacement).to.eq(` class ${className} { diff --git a/test/testHelper.ts b/test/testHelper.ts index 3876bee69..546789c92 100644 --- a/test/testHelper.ts +++ b/test/testHelper.ts @@ -1,13 +1,8 @@ -import * as tslint from 'tslint'; -import * as Lint from 'tslint'; import chai = require('chai'); -import * as ts from 'typescript'; -import { IOptions } from 'tslint'; +import { ILinterOptions, IOptions, Linter, LintResult, RuleFailure } from 'tslint/lib'; +import { SourceFile } from 'typescript/lib/typescript'; import { loadRules, convertRuleOptions } from './utils'; -const fs = require('fs'); -const path = require('path'); - interface ISourcePosition { line: number; character: number; @@ -32,41 +27,43 @@ export interface IExpectedFailure { * @param options additional options for the lint rule * @returns {LintResult} the result of linting */ -function lint(ruleName: string, source: string | ts.SourceFile, options: any): tslint.LintResult { +function lint(ruleName: string, source: string | SourceFile, options: any): LintResult { let configuration = { extends: [], - rules: new Map>(), - jsRules: new Map>(), + rules: new Map>(), + jsRules: new Map>(), rulesDirectory: [] }; if (!options) { options = []; } - const ops: Partial = { ruleName, ruleArguments: options, disabledIntervals: [] }; + const ops: Partial = { ruleName, ruleArguments: options, disabledIntervals: [] }; configuration.rules.set(ruleName, ops); - const linterOptions: tslint.ILinterOptions = { + const linterOptions: ILinterOptions = { formatter: 'json', rulesDirectory: './dist/src', - formattersDirectory: null, + formattersDirectory: undefined, fix: false }; - let linter = new tslint.Linter(linterOptions, undefined); + let linter = new Linter(linterOptions, undefined); if (typeof source === 'string') { linter.lint('file.ts', source, configuration); } else { const rules = loadRules(convertRuleOptions(configuration.rules), linterOptions.rulesDirectory, false); - const res = [].concat.apply([], rules.map(r => r.apply(source))) as tslint.RuleFailure[]; + const res = [].concat.apply([], rules.map(r => r.apply(source))) as RuleFailure[]; const errCount = res.filter(r => !r.getRuleSeverity || r.getRuleSeverity() === 'error').length; + return { errorCount: errCount, warningCount: res.length - errCount, output: '', - format: null, + format: '', fixes: [].concat.apply(res.map(r => r.getFix())), failures: res }; } + return linter.getResult(); } @@ -112,41 +109,48 @@ export interface AssertMultipleConfigs { * failures where there are multiple invalid characters. * @returns {{source: string, failure: {message: string, startPosition: null, endPosition: any}}} */ -const parseInvalidSource = (source: string, message: string, specialChar: string = '~', otherChars: string[] = []) => { +const parseInvalidSource = (source: string, message: string, specialChar = '~', otherChars: string[] = []) => { otherChars.forEach(char => source.replace(new RegExp(char, 'g'), ' ')); - let start = null; + + let start; let end; let line = 0; let col = 0; let lastCol = 0; let lastLine = 0; + for (let i = 0; i < source.length; i += 1) { - if (source[i] === specialChar && source[i - 1] !== '/' && start === null) { + if (source[i] === specialChar && source[i - 1] !== '/' && start === undefined) { start = { line: line - 1, character: col }; } + if (source[i] === '\n') { col = 0; line += 1; } else { col += 1; } + if (source[i] === specialChar && source[i - 1] !== '/') { lastCol = col; lastLine = line - 1; } } + end = { line: lastLine, character: lastCol }; + source = source.replace(new RegExp(specialChar, 'g'), ''); + return { - source: source, + source, failure: { - message: message, + message, startPosition: start, endPosition: end } @@ -160,40 +164,49 @@ const parseInvalidSource = (source: string, message: string, specialChar: string * @param config */ export function assertAnnotated(config: AssertConfig) { - if (config.message) { - const parsed = parseInvalidSource(config.source, config.message); - return assertFailure(config.ruleName, parsed.source, parsed.failure, config.options); - } else { - return assertSuccess(config.ruleName, config.source, config.options); + const { message, options, ruleName, source: sourceConfig } = config; + + if (message) { + const { failure, source } = parseInvalidSource(sourceConfig, message); + + return assertFailure(ruleName, source, failure, options); } + + return assertSuccess(ruleName, sourceConfig, options); } /** * Helper function which asserts multiple annotated failures. * @param configs */ -export function assertMultipleAnnotated(configs: AssertMultipleConfigs): Lint.RuleFailure[] { +export function assertMultipleAnnotated(configs: AssertMultipleConfigs): RuleFailure[] { + const { failures, options, ruleName, source } = configs; + return [].concat.apply( [], - configs.failures + failures .map((failure, index) => { - const otherCharacters = configs.failures.map(message => message.char).filter(x => x !== failure.char); - if (failure.msg) { - const parsed = parseInvalidSource(configs.source, failure.msg, failure.char, otherCharacters); - return assertFailure(configs.ruleName, parsed.source, parsed.failure, configs.options, index).filter(f => { - const start = f.getStartPosition().getLineAndCharacter(); - const end = f.getEndPosition().getLineAndCharacter(); - return ( - start.character === parsed.failure.startPosition.character && - start.line === parsed.failure.endPosition.line && - end.character === parsed.failure.endPosition.character && - end.line === parsed.failure.endPosition.line - ); - }); - } else { - assertSuccess(configs.ruleName, configs.source, configs.options); + const otherCharacters = failures.map(message => message.char).filter(x => x !== failure.char); + + if (!failure.msg) { + assertSuccess(ruleName, source, options); + return null; } + + const { failure: parsedFailure, source: parsedSource } = parseInvalidSource(source, failure.msg, failure.char, otherCharacters); + + return (assertFailure(ruleName, parsedSource, parsedFailure, options, index) || []).filter(f => { + const { character: startChar, line: startLine } = f.getStartPosition().getLineAndCharacter(); + const { character: endChar, line: endLine } = f.getEndPosition().getLineAndCharacter(); + + return ( + startChar === parsedFailure.startPosition.character && + startLine === parsedFailure.endPosition.line && + endChar === parsedFailure.endPosition.character && + endLine === parsedFailure.endPosition.line + ); + }); }) .filter(r => r !== null) ); @@ -213,26 +226,29 @@ export function assertMultipleAnnotated(configs: AssertMultipleConfigs): Lint.Ru */ export function assertFailure( ruleName: string, - source: string | ts.SourceFile, + source: string | SourceFile, fail: IExpectedFailure, - options = null, - onlyNthFailure: number = 0 -): Lint.RuleFailure[] { - let result: Lint.LintResult; + options?: any, + onlyNthFailure = 0 +): RuleFailure[] | undefined { + let result: LintResult; + try { result = lint(ruleName, source, options); + + chai.assert(result.failures && result.failures.length > 0, 'no failures'); + + const ruleFail = result.failures[onlyNthFailure]; + + chai.assert.equal(fail.message, ruleFail.getFailure(), "error messages don't match"); + chai.assert.deepEqual(fail.startPosition, ruleFail.getStartPosition().getLineAndCharacter(), "start char doesn't match"); + chai.assert.deepEqual(fail.endPosition, ruleFail.getEndPosition().getLineAndCharacter(), "end char doesn't match"); + + return result ? result.failures : undefined; } catch (e) { console.log(e.stack); + return undefined; } - chai.assert(result.failures && result.failures.length > 0, 'no failures'); - const ruleFail = result.failures[onlyNthFailure]; - chai.assert.equal(fail.message, ruleFail.getFailure(), "error messages don't match"); - chai.assert.deepEqual(fail.startPosition, ruleFail.getStartPosition().getLineAndCharacter(), "start char doesn't match"); - chai.assert.deepEqual(fail.endPosition, ruleFail.getEndPosition().getLineAndCharacter(), "end char doesn't match"); - if (result) { - return result.failures; - } - return undefined; } /** @@ -244,13 +260,14 @@ export function assertFailure( * @param fails * @param options */ -export function assertFailures(ruleName: string, source: string | ts.SourceFile, fails: IExpectedFailure[], options = null) { +export function assertFailures(ruleName: string, source: string | SourceFile, fails: IExpectedFailure[], options?: any) { let result; try { result = lint(ruleName, source, options); } catch (e) { console.log(e.stack); } + chai.assert(result.failures && result.failures.length > 0, 'no failures'); result.failures.forEach((ruleFail, index) => { chai.assert.equal(fails[index].message, ruleFail.getFailure(), "error messages don't match"); @@ -266,7 +283,7 @@ export function assertFailures(ruleName: string, source: string | ts.SourceFile, * @param source * @param options */ -export function assertSuccess(ruleName: string, source: string | ts.SourceFile, options = null) { +export function assertSuccess(ruleName: string, source: string | SourceFile, options?: any) { const result = lint(ruleName, source, options); chai.assert.isTrue(result && result.failures.length === 0); } diff --git a/test/util/classDeclarationUtils.spec.ts b/test/util/classDeclarationUtils.spec.ts index af3b3e515..9ad177072 100644 --- a/test/util/classDeclarationUtils.spec.ts +++ b/test/util/classDeclarationUtils.spec.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import * as tslint from 'tslint'; +import * as Lint from 'tslint'; import { NgWalker } from '../../src/angular/ngWalker'; import { getDeclaredMethodNames, getDeclaredPropertyNames } from '../../src/util/classDeclarationUtils'; @@ -15,10 +15,10 @@ describe('ngWalker', () => { baz() {} } `; - let ruleArgs: tslint.IOptions = { + let ruleArgs: Lint.IOptions = { ruleName: 'foo', ruleArguments: ['foo'], - disabledIntervals: null, + disabledIntervals: [], ruleSeverity: 'warning' }; let properties: FlatSymbolTable = {}; @@ -31,7 +31,7 @@ describe('ngWalker', () => { } } - let sf = ts.createSourceFile('foo', source, null); + let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new ClassUtilWalker(sf, ruleArgs); walker.walk(sf); chai.expect(methods).to.deep.eq({ bar: true, baz: true }); diff --git a/test/utils.ts b/test/utils.ts index 59b72e214..b64eb05bb 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -7,9 +7,9 @@ export function convertRuleOptions(ruleConfiguration: Map { const options: IOptions = { disabledIntervals: [], // deprecated, so just provide an empty array. - ruleArguments: ruleArguments !== null ? ruleArguments : [], + ruleArguments: ruleArguments || [], ruleName, - ruleSeverity: ruleSeverity !== null ? ruleSeverity : 'error' + ruleSeverity: ruleSeverity || 'error' }; output.push(options); }); @@ -48,21 +48,23 @@ function loadRule(directory: string, ruleName: string): any | 'not-found' { } export function getRelativePath(directory?: string | null, relativeTo?: string) { - if (directory !== null) { + if (directory !== null && directory !== undefined) { const basePath = relativeTo !== undefined ? relativeTo : process.cwd(); + return path.resolve(basePath, directory); } + return undefined; } export function arrayify(arg?: T | T[]): T[] { if (Array.isArray(arg)) { return arg; - } else if (arg !== null) { + } else if (arg !== null && arg !== undefined) { return [arg]; - } else { - return []; } + + return []; } function loadCachedRule(directory: string, ruleName: string, isCustomPath?: boolean): any | undefined { @@ -95,11 +97,13 @@ export function find(inputs: T[], getResult: (t: T) => U | undefined): U | return result; } } + return undefined; } function findRule(name: string, rulesDirectories?: string | string[]): any | undefined { const camelizedName = transformName(name); + return find(arrayify(rulesDirectories), dir => loadCachedRule(dir, camelizedName, true)); } @@ -122,10 +126,12 @@ export function loadRules(ruleOptionsList: IOptions[], rulesDirectories?: string notAllowedInJsRules.push(ruleName); } else { const rule = new Rule(ruleOptions); + if (rule.isEnabled()) { rules.push(rule); } } } + return rules; } diff --git a/tsconfig.json b/tsconfig.json index cc23a0b1b..5e5dcb64e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "noLib": false, "outDir": "./dist", "removeComments": true, + "strict": true, "target": "es5", "typeRoots": ["./node_modules", "./node_modules/@types"], "types": ["chai", "mocha", "node", "sprintf-js"]