From 7140df2284ceac709feb3e9252fd0c7cf62a6e3b Mon Sep 17 00:00:00 2001 From: Rafael Date: Thu, 7 Jun 2018 21:17:20 -0300 Subject: [PATCH] refactor: use type guard instead of unnecessary or incorrect assertions --- src/angular/config.ts | 22 +-- src/angular/metadata.ts | 21 ++- src/angular/metadataReader.ts | 42 ++--- src/angular/ngWalker.ts | 99 +++++------ src/angular/sourceMappingVisitor.ts | 4 +- src/angular/styles/cssAst.ts | 2 +- src/angular/styles/cssParser.ts | 6 +- .../templates/basicTemplateAstVisitor.ts | 15 +- .../recursiveAngularExpressionVisitor.ts | 2 +- src/angular/templates/templateParser.ts | 4 +- src/angular/urlResolvers/abstractResolver.ts | 45 ++--- src/angularWhitespaceRule.ts | 16 +- src/bananaInBoxRule.ts | 2 +- src/directiveClassSuffixRule.ts | 6 +- src/i18nRule.ts | 18 +- src/maxInlineDeclarationsRule.ts | 18 +- src/noAttributeParameterDecoratorRule.ts | 10 +- src/noConflictingLifeCycleHooksRule.ts | 19 +-- src/noInputRenameRule.ts | 9 +- src/noOutputNamedAfterStandardEventRule.ts | 21 ++- src/noOutputOnPrefixRule.ts | 19 ++- src/noUnusedCssRule.ts | 75 ++++----- src/pipeImpureRule.ts | 34 ++-- src/pipeNamingRule.ts | 27 ++- src/propertyDecoratorBase.ts | 45 ++--- src/selectorNameBase.ts | 81 +++++---- src/templatesNoNegatedAsyncRule.ts | 2 +- src/trackByFunctionRule.ts | 8 +- src/useLifeCycleInterfaceRule.ts | 6 +- src/usePipeDecoratorRule.ts | 4 +- src/usePipeTransformInterfaceRule.ts | 4 +- src/useViewEncapsulationRule.ts | 4 +- src/util/astQuery.ts | 27 +-- src/util/classDeclarationUtils.ts | 28 ++-- src/util/ngQuery.ts | 8 +- src/util/utils.ts | 83 +++++----- src/walkerFactory/walkerFactory.ts | 4 +- src/walkerFactory/walkerFn.ts | 2 +- test/angular/basicCssAstVisitor.spec.ts | 12 +- test/angular/metadataReader.spec.ts | 154 +++++++++--------- test/angular/ngWalker.spec.ts | 47 +++--- test/angular/sourceMappingVisitor.spec.ts | 14 +- test/angular/urlResolvers/urlResolver.spec.ts | 24 +-- test/componentClassSuffixRule.spec.ts | 2 +- test/noUnusedCssRule.spec.ts | 4 +- test/testHelper.ts | 20 +-- test/util/classDeclarationUtils.spec.ts | 6 +- 47 files changed, 537 insertions(+), 588 deletions(-) diff --git a/src/angular/config.ts b/src/angular/config.ts index a00b4d24e..e708e3408 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -35,6 +35,10 @@ export interface DirectiveDeclaration { let BUILD_TYPE = '<%= BUILD_TYPE %>'; +const transform = (code: string, extension: '.css' | '.html', url?: string): { code: string; url?: string } => { + return { code: !url || url.endsWith(extension) ? code : '', url }; +}; + export const Config: Config = { interpolation: ['{{', '}}'], @@ -71,25 +75,13 @@ export const Config: Config = { 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 }; - } + transformStyle: (code: string, url?: string) => transform(code, '.css', url), - return { code: '', url }; - } + transformTemplate: (code: string, url?: string) => transform(code, '.html', url) }; try { const root = require('app-root-path'); const newConfig = require(root.path + '/.codelyzer'); Object.assign(Config, newConfig); -} catch (e) {} +} catch {} diff --git a/src/angular/metadata.ts b/src/angular/metadata.ts index c01a812cf..9d9fffdbb 100644 --- a/src/angular/metadata.ts +++ b/src/angular/metadata.ts @@ -25,13 +25,22 @@ export interface TemplateMetadata extends PropertyMetadata { } export class DirectiveMetadata { - controller!: ts.ClassDeclaration; - decorator!: ts.Decorator; - selector!: string; + constructor( + public readonly controller: ts.ClassDeclaration, + public readonly decorator: ts.Decorator, + public readonly selector?: string + ) {} } export class ComponentMetadata extends DirectiveMetadata { - animations!: AnimationMetadata[]; - styles!: StyleMetadata[]; - template!: TemplateMetadata; + constructor( + public readonly controller: ts.ClassDeclaration, + public readonly decorator: ts.Decorator, + public readonly selector?: string, + public readonly animations?: (AnimationMetadata | undefined)[], + public readonly styles?: (StyleMetadata | undefined)[], + public readonly template?: TemplateMetadata + ) { + super(controller, decorator, selector); + } } diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index 7e30d49c8..ca53265ae 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -1,16 +1,9 @@ import * as ts from 'typescript'; -import { - callExpression, - decoratorArgument, - getStringInitializerFromProperty, - hasProperties, - isSimpleTemplateString, - withIdentifier -} from '../util/astQuery'; +import { callExpression, decoratorArgument, getStringInitializerFromProperty, hasProperties, withIdentifier } from '../util/astQuery'; import { ifTrue, listToMaybe, Maybe, unwrapFirst } from '../util/function'; import { logger } from '../util/logger'; import { getAnimations, getInlineStyle, getTemplate } from '../util/ngQuery'; -import { maybeNodeArray } from '../util/utils'; +import { isSimpleTemplateString, maybeNodeArray } from '../util/utils'; import { Config } from './config'; import { FileResolver } from './fileResolver/fileResolver'; import { AnimationMetadata, CodeWithSourceMap, ComponentMetadata, DirectiveMetadata, StyleMetadata, TemplateMetadata } from './metadata'; @@ -60,38 +53,37 @@ export class MetadataReader { .bind(expr => getStringInitializerFromProperty('selector', expr!.properties)) .fmap(initializer => initializer!.text); - return Object.assign(new DirectiveMetadata(), { - controller: d, - decorator: dec, - selector: selector.unwrap() - }); + return new DirectiveMetadata(d, dec, selector.unwrap()); } 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 animations_M = external_M.bind(external => this.readComponentAnimationsMetadata(dec, external!)); + const animations_M = external_M.bind(() => this.readComponentAnimationsMetadata(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, { - animations: animations_M.unwrap(), - styles: style_M.unwrap(), - template: template_M.unwrap() - }); + return new ComponentMetadata( + directiveMetadata.controller, + directiveMetadata.decorator, + directiveMetadata.selector, + animations_M.unwrap(), + style_M.unwrap(), + template_M.unwrap() + ); } protected getDecoratorArgument(decorator: ts.Decorator): Maybe { return decoratorArgument(decorator).bind(ifTrue(hasProperties)); } - protected readComponentAnimationsMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe { + protected readComponentAnimationsMetadata(dec: ts.Decorator): Maybe<(AnimationMetadata | undefined)[] | undefined> { return getAnimations(dec).fmap(inlineAnimations => inlineAnimations!.elements .filter(inlineAnimation => isSimpleTemplateString(inlineAnimation)) .map(inlineAnimation => ({ - animation: normalizeTransformed({ code: (inlineAnimation as ts.StringLiteral | ts.NoSubstitutionTemplateLiteral).text }), + animation: normalizeTransformed({ code: (inlineAnimation as ts.StringLiteralLike).text }), node: inlineAnimation as ts.Node })) ); @@ -122,8 +114,8 @@ export class MetadataReader { .fmap(inlineStyles => // Resolve Inline styles inlineStyles!.elements.filter(inlineStyle => isSimpleTemplateString(inlineStyle)).map(inlineStyle => ({ - node: inlineStyle as ts.Node, - style: normalizeTransformed(Config.transformStyle((inlineStyle as ts.StringLiteral | ts.NoSubstitutionTemplateLiteral).text)) + node: inlineStyle, + style: normalizeTransformed(Config.transformStyle((inlineStyle as ts.StringLiteralLike).text)) })) ) .catch(() => @@ -148,7 +140,7 @@ export class MetadataReader { private _resolve(url: string): Maybe { try { return Maybe.lift(this._fileResolver.resolve(url)); - } catch (e) { + } catch { logger.info('Cannot read file' + url); return Maybe.nothing; } diff --git a/src/angular/ngWalker.ts b/src/angular/ngWalker.ts index d71978129..8f8ec059c 100644 --- a/src/angular/ngWalker.ts +++ b/src/angular/ngWalker.ts @@ -15,11 +15,22 @@ import { RecursiveAngularExpressionVisitor } from './templates/recursiveAngularE import { ReferenceCollectorVisitor } from './templates/referenceCollectorVisitor'; import { parseTemplate } from './templates/templateParser'; -const getDecoratorStringArgs = (decorator: ts.Decorator): (string | null)[] => { +const getDecoratorStringArgs = (decorator: ts.Decorator): string[] => { const { expression } = decorator; const args = ts.isCallExpression(expression) ? expression.arguments : ts.createNodeArray(); - return args.map(a => (ts.isStringLiteral(a) ? a.text : null)); + return args.filter(ts.isStringLiteral).map(x => x.text); +}; + +const getPosition = (node: ts.Node) => { + let pos = 0; + if (node) { + pos = node.pos + 1; + try { + pos = node.getStart() + 1; + } catch {} + } + return pos; }; export interface NgWalkerConfig { @@ -55,24 +66,24 @@ export class NgWalker extends Lint.RuleWalker { } else if (metadata instanceof DirectiveMetadata) { this.visitNgDirective(metadata); } - maybeNodeArray(>declaration.decorators).forEach(this.visitClassDecorator.bind(this)); + maybeNodeArray(ts.createNodeArray(declaration.decorators)).forEach(this.visitClassDecorator.bind(this)); super.visitClassDeclaration(declaration); } visitMethodDeclaration(method: ts.MethodDeclaration) { - maybeNodeArray(>method.decorators).forEach(this.visitMethodDecorator.bind(this)); + maybeNodeArray(ts.createNodeArray(method.decorators)).forEach(this.visitMethodDecorator.bind(this)); super.visitMethodDeclaration(method); } visitPropertyDeclaration(prop: ts.PropertyDeclaration) { - maybeNodeArray(>prop.decorators).forEach(this.visitPropertyDecorator.bind(this)); + maybeNodeArray(ts.createNodeArray(prop.decorators)).forEach(this.visitPropertyDecorator.bind(this)); super.visitPropertyDeclaration(prop); } protected visitMethodDecorator(decorator: ts.Decorator) { let name = getDecoratorName(decorator); if (name === 'HostListener') { - this.visitNgHostListener(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgHostListener(decorator.parent as ts.MethodDeclaration, decorator, getDecoratorStringArgs(decorator)); } } @@ -80,25 +91,25 @@ export class NgWalker extends Lint.RuleWalker { let name = getDecoratorName(decorator); switch (name) { case 'Input': - this.visitNgInput(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgInput(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; case 'Output': - this.visitNgOutput(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgOutput(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; case 'HostBinding': - this.visitNgHostBinding(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgHostBinding(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; case 'ContentChild': - this.visitNgContentChild(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgContentChild(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; case 'ContentChildren': - this.visitNgContentChildren(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgContentChildren(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; case 'ViewChild': - this.visitNgViewChild(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgViewChild(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; case 'ViewChildren': - this.visitNgViewChildren(decorator.parent, decorator, getDecoratorStringArgs(decorator)); + this.visitNgViewChildren(decorator.parent as ts.PropertyDeclaration, decorator, getDecoratorStringArgs(decorator)); break; } } @@ -107,56 +118,48 @@ export class NgWalker extends Lint.RuleWalker { let name = getDecoratorName(decorator); if (name === 'Injectable') { - this.visitNgInjectable(decorator.parent, decorator); + this.visitNgInjectable(decorator.parent as ts.ClassDeclaration, decorator); } if (name === 'Pipe') { - this.visitNgPipe(decorator.parent, decorator); + this.visitNgPipe(decorator.parent as ts.ClassDeclaration, decorator); } if (name === 'NgModule') { this.visitNgModule(decorator); } - - // Not invoked @Component or @Pipe, or @Directive - if ( - !(decorator.expression).arguments || - !(decorator.expression).arguments.length || - !((decorator.expression).arguments[0]).properties - ) { - return; - } } protected visitNgComponent(metadata: ComponentMetadata) { - const template = metadata.template; - const getPosition = (node: ts.Node) => { - let pos = 0; - if (node) { - pos = node.pos + 1; - try { - pos = node.getStart() + 1; - } catch (e) {} - } - return pos; - }; - const { styles = [] } = metadata; for (const style of styles) { try { - this.visitNgStyleHelper(parseCss(style.style.code), metadata, style, getPosition(style.node!)); + const cssAst = parseCss(style!.style.code); + this.visitNgStyleHelper(cssAst, metadata, style!, getPosition(style!.node!)); } catch (e) { - logger.error('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text, e); + const { + controller: { name } + } = metadata; + const text = name && ts.isIdentifier(name) ? name.text : ''; + + logger.error('Cannot parse the styles of', text, e); } } + const { template } = metadata; + if (template && template.template) { try { const templateAst = parseTemplate(template.template.code, Config.predefinedDirectives); this.visitNgTemplateHelper(templateAst, metadata, getPosition(template.node!)); } catch (e) { - logger.error('Cannot parse the template of', ((metadata.controller || {}).name || {}).text, e); + const { + controller: { name } + } = metadata; + const text = name && ts.isIdentifier(name) ? name.text : ''; + + logger.error('Cannot parse the template of', text, e); } } } @@ -169,28 +172,28 @@ export class NgWalker extends Lint.RuleWalker { protected visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) {} - protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} + protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} - protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: (string | null)[]) {} + protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) {} - protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: (string | null)[]) {} + protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: string[]) {} - protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: (string | null)[]) {} + protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: string[]) {} - protected visitNgContentChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} + protected visitNgContentChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} - protected visitNgContentChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} + protected visitNgContentChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} - protected visitNgViewChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} + protected visitNgViewChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} - protected visitNgViewChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: (string | null)[]) {} + protected visitNgViewChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {} protected visitNgTemplateHelper(roots: compiler.TemplateAst[], context: ComponentMetadata, baseStart: number) { if (!roots || !roots.length) { return; } - const sourceFile = this.getContextSourceFile(context.template.url!, context.template.template.source!); + const sourceFile = this.getContextSourceFile(context.template!.url!, context.template!.template.source!); const referenceVisitor = new ReferenceCollectorVisitor(); const visitor = new this._config!.templateVisitorCtrl!( sourceFile, diff --git a/src/angular/sourceMappingVisitor.ts b/src/angular/sourceMappingVisitor.ts index 05f652c42..1194eb3b3 100644 --- a/src/angular/sourceMappingVisitor.ts +++ b/src/angular/sourceMappingVisitor.ts @@ -64,7 +64,7 @@ function computeLineAndCharacterOfPosition(lineStarts: number[], position: numbe } function computeLineStarts(text: string): number[] { - const result: number[] = new Array(); + const result: number[] = []; let pos = 0; let lineStart = 0; while (pos < text.length) { @@ -95,7 +95,7 @@ export class SourceMappingVisitor extends RuleWalker { parentAST: any; private consumer: SourceMapConsumer; - constructor(sourceFile: ts.SourceFile, options: IOptions, protected codeWithMap: CodeWithSourceMap, protected basePosition: number) { + constructor(sourceFile: ts.SourceFile, options: IOptions, public codeWithMap: CodeWithSourceMap, protected basePosition: number) { super(sourceFile, options); if (this.codeWithMap.map) { this.consumer = new SourceMapConsumer(this.codeWithMap.map); diff --git a/src/angular/styles/cssAst.ts b/src/angular/styles/cssAst.ts index 7c35532be..cc09fe512 100644 --- a/src/angular/styles/cssAst.ts +++ b/src/angular/styles/cssAst.ts @@ -106,7 +106,7 @@ export class CssBlockDefinitionRuleAst extends CssBlockRuleAst { block: CssBlockAst ) { super(location, type, block); - const firstCssToken: CssToken = query.tokens[0]; + const firstCssToken = query.tokens[0]; this.name = new CssToken(firstCssToken.index, firstCssToken.column, firstCssToken.line, CssTokenType.Identifier, this.strValue); } visit(visitor: CssAstVisitor, context?: any): any { diff --git a/src/angular/styles/cssParser.ts b/src/angular/styles/cssParser.ts index 0b0b05dcc..5782129c7 100644 --- a/src/angular/styles/cssParser.ts +++ b/src/angular/styles/cssParser.ts @@ -518,7 +518,7 @@ export class CssParser { } // :host(a, b, c) { - this._parseSelectors(innerDelims).forEach((selector, index) => { + this._parseSelectors(innerDelims).forEach(selector => { innerSelectors.push(selector); }); } else { @@ -553,7 +553,7 @@ export class CssParser { const selectorCssTokens: CssToken[] = []; const pseudoSelectors: CssPseudoSelectorAst[] = []; - let previousToken: CssToken = undefined!; + let previousToken: CssToken | null = null!; const selectorPartDelimiters = delimiters | SPACE_DELIM_FLAG; let loopOverSelector = !characterContainsDelimiter(this._scanner!.peek, selectorPartDelimiters); @@ -736,7 +736,7 @@ export class CssParser { const start = this._getScannerIndex(); const tokens: CssToken[] = []; - let previous: CssToken = undefined!; + let previous: CssToken | null = null!; while (!characterContainsDelimiter(this._scanner!.peek, delimiters)) { let token: CssToken; if (previous != null && previous.type == CssTokenType.Identifier && this._scanner!.peek == chars.$LPAREN) { diff --git a/src/angular/templates/basicTemplateAstVisitor.ts b/src/angular/templates/basicTemplateAstVisitor.ts index d847fa138..6adc04429 100644 --- a/src/angular/templates/basicTemplateAstVisitor.ts +++ b/src/angular/templates/basicTemplateAstVisitor.ts @@ -8,7 +8,7 @@ import { ComponentMetadata } from '../metadata'; import { RecursiveAngularExpressionVisitor } from './recursiveAngularExpressionVisitor'; import { SourceMappingVisitor } from '../sourceMappingVisitor'; -const getExpressionDisplacement = (binding: any) => { +const getExpressionDisplacement = (binding: ast.TemplateAst) => { let attrLen = 0; let valLen = 0; let totalLength = 0; @@ -22,9 +22,8 @@ const getExpressionDisplacement = (binding: any) => { // the binding type. For event it is 0. (+1 because of the ".") let subBindingLen = 0; if (binding instanceof ast.BoundElementPropertyAst) { - let prop: ast.BoundElementPropertyAst = binding; // The length of the binding type - switch (prop.type) { + switch (binding.type) { case ast.PropertyBindingType.Animation: subBindingLen = 'animate'.length + 1; break; @@ -108,7 +107,7 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast protected templateStart: number, private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor ) { - super(sourceFile, _originalOptions, context.template.template, templateStart); + super(sourceFile, _originalOptions, context.template!.template, templateStart); } visit?(node: ast.TemplateAst, context: any): any { @@ -148,8 +147,8 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast } visitElementProperty(prop: ast.BoundElementPropertyAst, context: any): any { - const ast: any = (prop.value).ast; - ast.interpolateExpression = (prop.value).source; + const ast = (prop.value as e.ASTWithSource).ast; + (ast as any).interpolateExpression = (prop.value as any).source; this.visitNgTemplateAST(prop.value, this.templateStart + getExpressionDisplacement(prop), prop); } @@ -158,8 +157,8 @@ export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast visitBoundText(text: ast.BoundTextAst, context: any): any { if (ExpTypes.ASTWithSource(text.value)) { // Note that will not be reliable for different interpolation symbols - const ast: any = (text.value).ast; - ast.interpolateExpression = (text.value).source; + const ast = (text.value as e.ASTWithSource).ast; + (ast as any).interpolateExpression = (text.value as any).source; this.visitNgTemplateAST(ast, this.templateStart + getExpressionDisplacement(text)); } } diff --git a/src/angular/templates/recursiveAngularExpressionVisitor.ts b/src/angular/templates/recursiveAngularExpressionVisitor.ts index 44bb61c3c..ebbb52847 100644 --- a/src/angular/templates/recursiveAngularExpressionVisitor.ts +++ b/src/angular/templates/recursiveAngularExpressionVisitor.ts @@ -13,7 +13,7 @@ export class RecursiveAngularExpressionVisitor extends SourceMappingVisitor impl public preDefinedVariables: FlatSymbolTable = {}; constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, protected context: ComponentMetadata, protected basePosition: number) { - super(sourceFile, options, context.template.template, basePosition); + super(sourceFile, options, context.template!.template, basePosition); } visit(ast: e.AST, context: any) { diff --git a/src/angular/templates/templateParser.ts b/src/angular/templates/templateParser.ts index 0b5731e21..8b6a9684b 100644 --- a/src/angular/templates/templateParser.ts +++ b/src/angular/templates/templateParser.ts @@ -50,7 +50,7 @@ let defaultDirectives: DirectiveDeclaration[] = []; export const parseTemplate = (template: string, directives: DirectiveDeclaration[] = []) => { defaultDirectives = directives.map(d => dummyMetadataFactory(d)); - const TemplateParser = compiler.TemplateParser; + const TemplateParser = compiler.TemplateParser as any; const expressionParser = new compiler.Parser(new compiler.Lexer()); const elementSchemaRegistry = new compiler.DomElementSchemaRegistry(); const ngConsole = new Console(); @@ -131,7 +131,7 @@ export const parseTemplate = (template: string, directives: DirectiveDeclaration value: '', identifier: null }; - let result: any = null; + let result; try { SemVerDSL.lt('4.1.0', () => { result = tmplParser.tryParse( diff --git a/src/angular/urlResolvers/abstractResolver.ts b/src/angular/urlResolvers/abstractResolver.ts index 4ee5a5886..c0e37ec02 100644 --- a/src/angular/urlResolvers/abstractResolver.ts +++ b/src/angular/urlResolvers/abstractResolver.ts @@ -1,6 +1,5 @@ import * as ts from 'typescript'; -import { isPropertyAssignment } from 'typescript/lib/typescript'; -import { isSimpleTemplateString } from '../../util/utils'; +import { getDecoratorArgument, isSimpleTemplateString } from '../../util/utils'; export interface MetadataUrls { templateUrl: string; @@ -10,55 +9,39 @@ export interface MetadataUrls { export abstract class AbstractResolver { abstract resolve(decorator: ts.Decorator): MetadataUrls | null; - protected getTemplateUrl(decorator: ts.Decorator): string | null { - const arg = this.getDecoratorArgument(decorator); + protected getTemplateUrl(decorator: ts.Decorator): string | undefined { + const arg = getDecoratorArgument(decorator); if (!arg) { - return null; + return undefined; } - const prop = arg.properties - .filter(p => (p.name as any).text === 'templateUrl' && isSimpleTemplateString((p as ts.PropertyAssignment).initializer)) - .pop(); + const prop = arg.properties.find( + p => (p.name as ts.StringLiteralLike).text === 'templateUrl' && isSimpleTemplateString((p as ts.PropertyAssignment).initializer) + ); // 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; + return prop ? ((prop as ts.PropertyAssignment).initializer as ts.StringLiteralLike).text : undefined; } protected getStyleUrls(decorator: ts.Decorator): string[] { - const arg = this.getDecoratorArgument(decorator); + const arg = getDecoratorArgument(decorator); if (!arg) { return []; } - const prop = arg.properties - .filter( - p => (p.name as any).text === 'styleUrls' && isPropertyAssignment(p) && p.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression - ) - .pop(); + const prop = arg.properties.find( + p => (p.name as any).text === 'styleUrls' && ts.isPropertyAssignment(p) && ts.isArrayLiteralExpression(p.initializer) + ); if (prop) { return ((prop as ts.PropertyAssignment).initializer as ts.ArrayLiteralExpression).elements - .filter(e => isSimpleTemplateString(e)) - .map(e => (e as any).text); + .filter(isSimpleTemplateString) + .map(e => (e as ts.StringLiteralLike).text); } return []; } - - protected getDecoratorArgument(decorator: ts.Decorator): ts.ObjectLiteralExpression | null { - const expr = decorator.expression; - - if (expr && expr.arguments && expr.arguments.length) { - const arg = expr.arguments[0] as ts.ObjectLiteralExpression; - - if (arg.properties && arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { - return arg; - } - } - - return null; - } } diff --git a/src/angularWhitespaceRule.ts b/src/angularWhitespaceRule.ts index 47b7ad380..a7a935f6b 100644 --- a/src/angularWhitespaceRule.ts +++ b/src/angularWhitespaceRule.ts @@ -168,24 +168,24 @@ class WhitespaceTemplateVisitor extends BasicTemplateAstVisitor { new SemicolonTemplateVisitor(this.getSourceFile(), this.getOptions(), this.context, this.templateStart) ]; - visitBoundText(text: ast.BoundTextAst, context: any): any { + visitBoundText(text: ast.BoundTextAst, context: BasicTemplateAstVisitor): any { const options = this.getOptions(); this.visitors .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitBoundText(text, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); super.visitBoundText(text, context); } - visitDirectiveProperty(prop: ast.BoundDirectivePropertyAst, context: any): any { + visitDirectiveProperty(prop: ast.BoundDirectivePropertyAst, context: BasicTemplateAstVisitor): any { const options = this.getOptions(); this.visitors .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitDirectiveProperty(prop, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); @@ -257,10 +257,6 @@ class PipeWhitespaceVisitor extends RecursiveAngularExpressionVisitor implements getCheckOption(): CheckOption { return 'check-pipe'; } - - protected isAsyncBinding(expr: any) { - return expr instanceof ast.BindingPipe && expr.name === 'async'; - } } class TemplateExpressionVisitor extends RecursiveAngularExpressionVisitor { @@ -268,13 +264,13 @@ class TemplateExpressionVisitor extends RecursiveAngularExpressionVisitor { new PipeWhitespaceVisitor(this.getSourceFile(), this.getOptions(), this.context, this.basePosition) ]; - visitPipe(expr: ast.BindingPipe, context: any): any { + visitPipe(expr: ast.BindingPipe, context: BasicTemplateAstVisitor): any { const options = this.getOptions(); this.visitors .map(v => v.addParentAST(this.parentAST)) .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitPipe(expr, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); diff --git a/src/bananaInBoxRule.ts b/src/bananaInBoxRule.ts index 27b993e53..75d1869f3 100644 --- a/src/bananaInBoxRule.ts +++ b/src/bananaInBoxRule.ts @@ -27,7 +27,7 @@ const getReplacements = (text: ast.BoundEventAst, absolutePosition: number) => { }; class BananaInBoxTemplateVisitor extends BasicTemplateAstVisitor { - visitEvent(prop: ast.BoundEventAst, context: any): any { + visitEvent(prop: ast.BoundEventAst, context: BasicTemplateAstVisitor): any { if (prop.name) { let error: string | null = null; diff --git a/src/directiveClassSuffixRule.ts b/src/directiveClassSuffixRule.ts index 7c042b85a..0e63471a1 100644 --- a/src/directiveClassSuffixRule.ts +++ b/src/directiveClassSuffixRule.ts @@ -2,7 +2,7 @@ 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 { getSymbolName } from './util/utils'; import { DirectiveMetadata } from './angular/metadata'; @@ -52,8 +52,8 @@ export class ClassMetadataWalker extends NgWalker { if ( i.length !== 0 && i[0].types - .map(getInterfaceName) - .filter(x => !!x) + .map(getSymbolName) + .filter(Boolean) .some(x => x.endsWith(ValidatorSuffix)) ) { suffixes.push(ValidatorSuffix); diff --git a/src/i18nRule.ts b/src/i18nRule.ts index 6e4d803fe..def7f1035 100644 --- a/src/i18nRule.ts +++ b/src/i18nRule.ts @@ -108,40 +108,40 @@ class I18NTemplateVisitor extends BasicTemplateAstVisitor { new I18NTextVisitor(this.getSourceFile(), this.getOptions(), this.context, this.templateStart) ]; - visit(a: any, context: any) { - super.visit!(a, context); + visit(node: ast.TemplateAst, context: BasicTemplateAstVisitor) { + super.visit!(node, context); } - visitAttr(attr: ast.AttrAst, context: any): any { + visitAttr(attr: ast.AttrAst, context: BasicTemplateAstVisitor): any { const options = this.getOptions(); this.visitors .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitAttr(attr, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); super.visitAttr(attr, context); } - visitElement(element: ast.ElementAst, context: any): any { + visitElement(element: ast.ElementAst, context: BasicTemplateAstVisitor): any { const options = this.getOptions(); this.visitors .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitElement(element, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); super.visitElement(element, context); } - visitText(text: ast.TextAst, context: any): any { + visitText(text: ast.TextAst, context: BasicTemplateAstVisitor): any { const options = this.getOptions(); this.visitors .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitText(text, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); @@ -153,7 +153,7 @@ class I18NTemplateVisitor extends BasicTemplateAstVisitor { this.visitors .filter(v => options.indexOf(v.getCheckOption()) >= 0) .map(v => v.visitBoundText(text, this)) - .filter(f => !!f) + .filter(Boolean) .forEach(f => this.addFailureFromStartToEnd(f.getStartPosition().getPosition(), f.getEndPosition().getPosition(), f.getFailure(), f.getFix()) ); diff --git a/src/maxInlineDeclarationsRule.ts b/src/maxInlineDeclarationsRule.ts index 33eb2bc66..05148c8d9 100644 --- a/src/maxInlineDeclarationsRule.ts +++ b/src/maxInlineDeclarationsRule.ts @@ -114,7 +114,9 @@ export class MaxInlineDeclarationsWalker extends NgWalker { private getInlineAnimationsLinesCount(metadata: ComponentMetadata): number { return (metadata.animations || []).reduce((previousValue, currentValue) => { - previousValue += this.getLinesCount(currentValue.animation.source); + if (currentValue && currentValue.animation) { + previousValue += this.getLinesCount(currentValue.animation.source); + } return previousValue; }, 0); @@ -129,14 +131,14 @@ export class MaxInlineDeclarationsWalker extends NgWalker { const failureMessage = getAnimationsFailure(linesCount, this.animationsLinesLimit); - for (const animation of metadata.animations) { - this.addFailureAtNode(animation.node!, failureMessage); + for (const animation of metadata.animations!) { + this.addFailureAtNode(animation!.node!, failureMessage); } } private getInlineStylesLinesCount(metadata: ComponentMetadata): number { return (metadata.styles || []).reduce((previousValue, currentValue) => { - if (!currentValue.url) { + if (currentValue && !currentValue.url) { previousValue += this.getLinesCount(currentValue.style.source); } @@ -153,13 +155,13 @@ export class MaxInlineDeclarationsWalker extends NgWalker { const failureMessage = getStylesFailure(linesCount, this.stylesLinesLimit); - for (const style of metadata.styles) { - this.addFailureAtNode(style.node!, failureMessage); + for (const style of metadata.styles!) { + this.addFailureAtNode(style!.node!, failureMessage); } } private getTemplateLinesCount(metadata: ComponentMetadata): number { - return this.hasInlineTemplate(metadata) ? this.getLinesCount(metadata.template.template.source) : 0; + return this.hasInlineTemplate(metadata) ? this.getLinesCount(metadata.template!.template.source) : 0; } private hasInlineTemplate(metadata: ComponentMetadata): boolean { @@ -175,6 +177,6 @@ export class MaxInlineDeclarationsWalker extends NgWalker { const failureMessage = getTemplateFailure(linesCount, this.templateLinesLimit); - this.addFailureAtNode(metadata.template.node!, failureMessage); + this.addFailureAtNode(metadata.template!.node!, failureMessage); } } diff --git a/src/noAttributeParameterDecoratorRule.ts b/src/noAttributeParameterDecoratorRule.ts index dd8714179..a53956607 100644 --- a/src/noAttributeParameterDecoratorRule.ts +++ b/src/noAttributeParameterDecoratorRule.ts @@ -26,10 +26,10 @@ export class Rule extends Lint.Rules.AbstractRule { validate(ts.SyntaxKind.Constructor)(node => { return Maybe.lift(node.parent) .fmap(parent => { - 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; + if (parent && ts.isClassExpression(parent) && (parent.parent as any).name) { + return (parent.parent as any).name.text; + } else if (parent && ts.isClassDeclaration(parent)) { + return parent.name!.text; } }) .bind(parentName => { @@ -38,7 +38,7 @@ export class Rule extends Lint.Rules.AbstractRule { 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 as any).fmap(() => new Failure(p, sprintf(Rule.FAILURE_STRING, parentName, text, text))); diff --git a/src/noConflictingLifeCycleHooksRule.ts b/src/noConflictingLifeCycleHooksRule.ts index a07dcef91..42071e0bc 100644 --- a/src/noConflictingLifeCycleHooksRule.ts +++ b/src/noConflictingLifeCycleHooksRule.ts @@ -1,6 +1,7 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; +import { getSymbolName } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { static metadata: Lint.IRuleMetadata = { @@ -35,19 +36,19 @@ export class ClassMetadataWalker extends Lint.RuleWalker { } private validateInterfaces(node: ts.ClassDeclaration): void { - if (!node.heritageClauses) { + const { heritageClauses } = node; + + if (!heritageClauses) { return; } - const interfacesClause = node.heritageClauses.find(h => h.token === ts.SyntaxKind.ImplementsKeyword); + const interfacesClauses = heritageClauses.find(h => h.token === ts.SyntaxKind.ImplementsKeyword); - if (!interfacesClause) { + if (!interfacesClauses) { return; } - const interfaces = interfacesClause.types.map((t: any) => { - return t.expression.name ? t.expression.name.text : t.expression.text; - }); + const interfaces = interfacesClauses.types.map(getSymbolName); const matchesAllHooks = lifecycleHooksMethods.every(l => interfaces.indexOf(l) !== -1); if (matchesAllHooks) { @@ -56,10 +57,8 @@ export class ClassMetadataWalker extends Lint.RuleWalker { } private validateMethods(node: ts.ClassDeclaration): void { - 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; - }); + const methodNames = node.members.filter(ts.isMethodDeclaration).map(m => m.name!.getText()); + const matchesAllHooks = lifecycleHooksMethods.every(l => methodNames.indexOf(`${hooksPrefix}${l}`) !== -1); if (matchesAllHooks) { this.addFailureAtNode(node, sprintf(Rule.FAILURE_STRING, node.name!.text)); diff --git a/src/noInputRenameRule.ts b/src/noInputRenameRule.ts index 36f18597c..17b553f59 100644 --- a/src/noInputRenameRule.ts +++ b/src/noInputRenameRule.ts @@ -3,6 +3,7 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import { DirectiveMetadata } from './angular/metadata'; import { NgWalker } from './angular/ngWalker'; +import { getClassName } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { static readonly metadata: Lint.IRuleMetadata = { @@ -47,13 +48,13 @@ export class InputMetadataWalker extends NgWalker { } private validateInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) { - const className = (property.parent as ts.PropertyAccessExpression).name.getText(); - const propertyName = property.name.getText(); + const className = getClassName(property)!; + const memberName = property.name.getText(); - if (args.length === 0 || this.canPropertyBeAliased(args[0], propertyName)) { + if (args.length === 0 || this.canPropertyBeAliased(args[0], memberName)) { return; } - this.addFailureAtNode(property, getFailureMessage(className, propertyName)); + this.addFailureAtNode(property, getFailureMessage(className, memberName)); } } diff --git a/src/noOutputNamedAfterStandardEventRule.ts b/src/noOutputNamedAfterStandardEventRule.ts index 26e199eb8..affbe070f 100644 --- a/src/noOutputNamedAfterStandardEventRule.ts +++ b/src/noOutputNamedAfterStandardEventRule.ts @@ -2,6 +2,7 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; import { NgWalker } from './angular/ngWalker'; +import { getClassName } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { static readonly metadata: Lint.IRuleMetadata = { @@ -198,15 +199,21 @@ export class OutputMetadataWalker extends NgWalker { ]); protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) { - const className = (property.parent as any).name.text; - const memberName = (property.name as any).text; - const outputName = args.length === 0 ? memberName : args[0]; + this.validateOutput(property, args); + super.visitNgOutput(property, output, args); + } + + private validateOutput(property: ts.PropertyDeclaration, args: string[]): void { + const className = getClassName(property); + const memberName = property.name.getText(); + const outputName = args[0] || memberName; - if (outputName && this.standardEventNames.get(outputName)) { - const failure = sprintf(Rule.FAILURE_STRING, className, memberName); - this.addFailureAtNode(property, failure); + if (!outputName || !this.standardEventNames.get(outputName)) { + return; } - super.visitNgOutput(property, output, args); + const failure = sprintf(Rule.FAILURE_STRING, className, memberName); + + this.addFailureAtNode(property, failure); } } diff --git a/src/noOutputOnPrefixRule.ts b/src/noOutputOnPrefixRule.ts index 53bb01a7e..fc41e5430 100644 --- a/src/noOutputOnPrefixRule.ts +++ b/src/noOutputOnPrefixRule.ts @@ -2,6 +2,7 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; import { NgWalker } from './angular/ngWalker'; +import { getClassName } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { static readonly metadata: Lint.IRuleMetadata = { @@ -25,12 +26,20 @@ export class Rule extends Lint.Rules.AbstractRule { class OutputWalker extends NgWalker { protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) { - const className = (property).parent.name.text; - const memberName = (property.name).text as string; + this.validateOutput(property); + super.visitNgOutput(property, output, args); + } + + private validateOutput(property: ts.PropertyDeclaration): void { + const className = getClassName(property); + const memberName = property.name.getText(); - if (memberName && memberName.startsWith('on') && !(memberName.length >= 3 && memberName[2] !== memberName[2].toUpperCase())) { - this.addFailureAtNode(property, sprintf(Rule.FAILURE_STRING, className, memberName)); + if (!memberName || !/on((?![a-z])|(?=$))/.test(memberName)) { + return; } - super.visitNgOutput(property, output, args); + + const failure = sprintf(Rule.FAILURE_STRING, className, memberName); + + this.addFailureAtNode(property, failure); } } diff --git a/src/noUnusedCssRule.ts b/src/noUnusedCssRule.ts index 8254405ca..0651dd197 100644 --- a/src/noUnusedCssRule.ts +++ b/src/noUnusedCssRule.ts @@ -7,40 +7,33 @@ import { BasicCssAstVisitor } from './angular/styles/basicCssAstVisitor'; import { CssAst, CssSelectorAst, CssSelectorRuleAst } from './angular/styles/cssAst'; import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor'; import { parseTemplate } from './angular/templates/templateParser'; -import { getComponentDecorator, getDecoratorPropertyInitializer } from './util/utils'; +import { getComponentDecorator, getDecoratorPropertyInitializer, getSymbolName } from './util/utils'; import { ComponentMetadata, StyleMetadata } from './angular/metadata'; import { logger } from './util/logger'; import { SemVerDSL } from './util/ngVersion'; -const CssSelectorTokenizer = require('css-selector-tokenizer'); +interface Strategy { + attribute(ast: ElementAst): boolean; + class(ast: ElementAst): boolean; + id(ast: ElementAst): boolean; +} -const getSymbolName = (t: any) => { - let expr = t; - if (t.expression) { - expr = t.expression; - } - if (t.expression && t.expression.name) { - expr = t.expression.name; - } - return expr.text; -}; +const CssSelectorTokenizer = require('css-selector-tokenizer'); const isEncapsulationEnabled = (encapsulation: any) => { if (!encapsulation) { return true; - } else { - // By default consider the encapsulation disabled - if (getSymbolName(encapsulation) !== 'ViewEncapsulation') { - return false; - } else { - const encapsulationType = encapsulation.name.text; - if (/^(Emulated|Native)$/.test(encapsulationType)) { - return true; - } - } } - return false; + + // By default consider the encapsulation disabled + if (getSymbolName(encapsulation) !== 'ViewEncapsulation') { + return false; + } + + const encapsulationType = encapsulation.name.text; + + return /^(Emulated|Native)$/.test(encapsulationType); }; // Initialize the selector accessors @@ -60,7 +53,8 @@ const lang = require('cssauron')({ .filter(b => b.type === PropertyBindingType.Class) .map(b => b.name) .join(' '); - const classAttr = node.attrs.filter(a => a.name.toLowerCase() === 'class').pop(); + const classAttr = node.attrs.find(a => a.name.toLowerCase() === 'class'); + return classAttr ? `${classAttr.value} ${classBindings}` : classBindings; }, parent(node: any) { @@ -70,7 +64,7 @@ const lang = require('cssauron')({ return node.children; }, attr(node: ElementAst, attr: string) { - const targetAttr = node.attrs.filter(a => a.name === attr).pop(); + const targetAttr = node.attrs.find(a => a.name === attr); return targetAttr ? targetAttr.value : undefined; } @@ -82,7 +76,7 @@ class ElementVisitor extends BasicTemplateAstVisitor { fn(ast); ast.children.forEach(c => { if (c instanceof ElementAst) { - (c).parentNode = ast; + (c as any).parentNode = ast; } this.visit!(c, fn); }); @@ -94,21 +88,18 @@ const hasSelector = (s: any, type: string) => { if (!s) { return false; } - if (s.type === 'selector' || s.type === 'selectors') { - return (s.nodes || []).some(n => hasSelector(n, type)); - } else { - return s.type === type; - } + + return s.type === 'selector' || s.type === 'selectors' ? (s.nodes || []).some(n => hasSelector(n, type)) : s.type === type; }; -const dynamicFilters = { - id(ast: ElementAst, selector: any) { +const dynamicFilters: Strategy = { + id(ast: ElementAst) { return (ast.inputs || []).some(i => i.name === 'id'); }, - attribute(ast: ElementAst, selector: any) { + attribute(ast: ElementAst) { return (ast.inputs || []).some(i => i.type === PropertyBindingType.Attribute); }, - class(ast: ElementAst, selector: any) { + class(ast: ElementAst) { return (ast.inputs || []).some(i => i.name === 'className' || i.name === 'ngClass'); } }; @@ -118,7 +109,7 @@ const dynamicFilters = { // - If has selector by class and any of the elements has a dynamically set class we just skip it. // - If has selector by attribute and any of the elements has a dynamically set attribute we just skip it. class ElementFilterVisitor extends BasicTemplateAstVisitor { - shouldVisit(ast: ElementAst, strategies: any, selectorTypes: any): boolean { + shouldVisit(ast: ElementAst, strategies: Strategy, selectorTypes: object): boolean { return ( Object.keys(strategies).every(s => { const strategy = strategies[s]; @@ -126,9 +117,9 @@ class ElementFilterVisitor extends BasicTemplateAstVisitor { }) && (ast.children || []).every( c => - (ast instanceof ElementAst && this.shouldVisit(c, strategies, selectorTypes)) || + (ast instanceof ElementAst && this.shouldVisit(c as ElementAst, strategies, selectorTypes)) || (ast instanceof EmbeddedTemplateAst && - (ast.children || []).every(c => this.shouldVisit(c, strategies, selectorTypes))) + (ast.children || []).every(c => this.shouldVisit(c as ElementAst, strategies, selectorTypes))) ) ); } @@ -211,7 +202,7 @@ class UnusedCssVisitor extends BasicCssAstVisitor { return a; }, {}); - if (!elementFilterVisitor.shouldVisit(this.templateAst, dynamicFilters, selectorTypesCache)) { + if (!elementFilterVisitor.shouldVisit(this.templateAst as ElementAst, dynamicFilters, selectorTypesCache)) { return true; } @@ -252,7 +243,7 @@ export class UnusedCssNgVisitor extends NgWalker { [], false, [], - parseTemplate(meta.template.template.code), + parseTemplate(meta.template!.template.code), 0, null, null @@ -267,7 +258,7 @@ export class UnusedCssNgVisitor extends NgWalker { [], [], false, - parseTemplate(meta.template.template.code), + parseTemplate(meta.template!.template.code), 0, null, null @@ -294,7 +285,7 @@ export class UnusedCssNgVisitor extends NgWalker { 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); + const d = getComponentDecorator(context.controller)!; const encapsulation = getDecoratorPropertyInitializer(d, 'encapsulation'); if (isEncapsulationEnabled(encapsulation)) { style.visit(visitor); diff --git a/src/pipeImpureRule.ts b/src/pipeImpureRule.ts index de9343497..580cb3b99 100644 --- a/src/pipeImpureRule.ts +++ b/src/pipeImpureRule.ts @@ -2,9 +2,10 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import { sprintf } from 'sprintf-js'; import { NgWalker } from './angular/ngWalker'; +import { getDecoratorArgument } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { - public static metadata: Lint.IRuleMetadata = { + static readonly metadata: Lint.IRuleMetadata = { ruleName: 'pipe-impure', type: 'functionality', description: 'Pipes cannot be declared as impure.', @@ -14,36 +15,33 @@ export class Rule extends Lint.Rules.AbstractRule { typescriptOnly: true }; - static FAILURE = 'Warning: impure pipe declared in class %s'; + static readonly FAILURE_STRING = 'Warning: impure pipe declared in class %s'; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new ClassMetadataWalker(sourceFile, this.getOptions())); } } export class ClassMetadataWalker extends NgWalker { protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) { - this.validateProperties(controller.name!.text, decorator); + this.validatePipe(controller.name!.text, decorator); super.visitNgPipe(controller, decorator); } - private validateProperties(className: string, pipe: any) { - let argument = this.extractArgument(pipe); - if (argument.kind === ts.SyntaxKind.ObjectLiteralExpression) { - argument.properties.filter(n => n.name.text === 'pure').forEach(this.validateProperty.bind(this, className)); + private validatePipe(className: string, decorator: ts.Decorator): void { + const argument = getDecoratorArgument(decorator)!; + const property = argument.properties.find(p => p.name!.getText() === 'pure'); + + if (!property) { + return; } - } - private extractArgument(pipe: any) { - let baseExpr = pipe.expression || {}; - let args = baseExpr.arguments || []; - return args[0]; - } + const propValue = ts.isPropertyAssignment(property) ? property.initializer.getText() : undefined; - private validateProperty(className: string, property: any) { - const propValue = property.initializer.getText(); - if (propValue === 'false') { - this.addFailureAtNode(property, sprintf(Rule.FAILURE, className)); + if (!propValue || propValue !== 'false') { + return; } + + this.addFailureAtNode(property, sprintf(Rule.FAILURE_STRING, className)); } } diff --git a/src/pipeNamingRule.ts b/src/pipeNamingRule.ts index f4af26494..2b3791415 100644 --- a/src/pipeNamingRule.ts +++ b/src/pipeNamingRule.ts @@ -3,6 +3,7 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import { NgWalker } from './angular/ngWalker'; import { SelectorValidator } from './util/selectorValidator'; +import { getDecoratorArgument } from './util/utils'; const OPTION_ATTRIBUTE = 'attribute'; const OPTION_CAMEL_CASE = 'camelCase'; @@ -102,29 +103,21 @@ export class ClassMetadataWalker extends NgWalker { } private validateProperties(className: string, pipe: ts.Decorator) { - let argument = this.extractArgument(pipe); - if (argument && argument.kind === ts.SyntaxKind.ObjectLiteralExpression) { - (argument).properties - .filter(n => n.name && (n.name).text === 'name') - .forEach(this.validateProperty.bind(this, className)); - } - } + const argument = getDecoratorArgument(pipe)!; - private extractArgument(pipe: ts.Decorator): ts.Expression | undefined { - const baseExpr = pipe.expression; - if (baseExpr.arguments) { - const args = baseExpr.arguments; - return args[0]; - } - return undefined; + argument.properties + .filter(p => p.name && ts.isIdentifier(p.name) && p.name.text === 'name') + .forEach(this.validateProperty.bind(this, className)); } private validateProperty(className: string, property: ts.Node) { - const init = (property).initializer; - if (init && (init).text) { - const propName = (init).text; + const initializer = ts.isPropertyAssignment(property) ? property.initializer : undefined; + + if (initializer && ts.isStringLiteral(initializer)) { + const propName = initializer.text; const isValidName = this.rule.validateName(propName); const isValidPrefix = this.rule.hasPrefix ? this.rule.validatePrefix(propName) : true; + if (!isValidName || !isValidPrefix) { this.addFailureAtNode(property, this.getFailureMessage(className, propName)); } diff --git a/src/propertyDecoratorBase.ts b/src/propertyDecoratorBase.ts index 96f033edb..e9be0ace4 100644 --- a/src/propertyDecoratorBase.ts +++ b/src/propertyDecoratorBase.ts @@ -1,7 +1,7 @@ +import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; -import { sprintf } from 'sprintf-js'; -import { IOptions } from 'tslint'; +import { getDecoratorArgument, getDecoratorName } from './util/utils'; export interface IUsePropertyDecoratorConfig { propertyName: string; @@ -10,17 +10,20 @@ export interface IUsePropertyDecoratorConfig { } export class UsePropertyDecorator extends Lint.Rules.AbstractRule { - public static formatFailureString(config: IUsePropertyDecoratorConfig, decoratorName: string, className: string) { - let decorators = config.decoratorName; - if (decorators instanceof Array) { - decorators = (decorators).map(d => `"@${d}"`).join(', '); + public static formatFailureString(config: IUsePropertyDecoratorConfig, decoratorStr: string, className: string) { + const { decoratorName, errorMessage, propertyName } = config; + let decorators: string | string[]; + + if (decoratorName instanceof Array) { + decorators = decoratorName.map(d => `"@${d}"`).join(', '); } else { - decorators = `"@${decorators}"`; + decorators = `"@${decoratorName}"`; } - return sprintf(config.errorMessage, decoratorName, className, config.propertyName, decorators); + + return sprintf(errorMessage, decoratorStr, className, propertyName, decorators); } - constructor(private config: IUsePropertyDecoratorConfig, options: IOptions) { + constructor(private config: IUsePropertyDecoratorConfig, options: Lint.IOptions) { super(options); } @@ -35,26 +38,26 @@ class DirectiveMetadataWalker extends Lint.RuleWalker { } visitClassDeclaration(node: ts.ClassDeclaration) { - (>node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text)); + ts.createNodeArray(node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text)); super.visitClassDeclaration(node); } private validateDecorator(className: string, decorator: ts.Decorator) { - let baseExpr = decorator.expression || {}; - let expr = baseExpr.expression || {}; - let name = expr.text; - let args = baseExpr.arguments || []; - let arg = args[0]; - if (/^(Component|Directive)$/.test(name) && arg) { - this.validateProperty(className, name, arg); + const argument = getDecoratorArgument(decorator)!; + const name = getDecoratorName(decorator); + + if (name && argument && /^(Component|Directive)$/.test(name)) { + this.validateProperty(className, name, argument); } } private validateProperty(className: string, decoratorName: string, arg: ts.ObjectLiteralExpression) { - 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)); - }); + if (!ts.isObjectLiteralExpression(arg)) { + return; } + + 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 8a075a7d7..3e0a8b2f5 100644 --- a/src/selectorNameBase.ts +++ b/src/selectorNameBase.ts @@ -1,8 +1,9 @@ +import * as compiler from '@angular/compiler'; +import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; -import { SelectorValidator } from './util/selectorValidator'; import * as ts from 'typescript'; -import { sprintf } from 'sprintf-js'; -import * as compiler from '@angular/compiler'; +import { SelectorValidator } from './util/selectorValidator'; +import { getDecoratorArgument, getDecoratorName } from './util/utils'; export type SelectorType = 'element' | 'attribute'; export type SelectorTypeInternal = 'element' | 'attrs'; @@ -88,7 +89,7 @@ export abstract class SelectorRule extends Lint.Rules.AbstractRule { } return prop; }) - .filter(s => !!s) + .filter(Boolean) ); }) ); @@ -101,58 +102,56 @@ export class SelectorValidatorWalker extends Lint.RuleWalker { } visitClassDeclaration(node: ts.ClassDeclaration) { - if (node.decorators) { - (>node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text)); - } + ts.createNodeArray(node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text)); super.visitClassDeclaration(node); } private validateDecorator(className: string, decorator: ts.Decorator) { - let baseExpr = decorator.expression || {}; - let expr = baseExpr.expression || {}; - let name = expr.text; - let args = baseExpr.arguments || []; - let arg = args[0]; + const argument = getDecoratorArgument(decorator)!; + const name = getDecoratorName(decorator); + // Do not run component rules for directives if (this.rule.handleType === name) { - this.validateSelector(className, arg); + this.validateSelector(className, argument); } } private validateSelector(className: string, arg: ts.Node) { - if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) { - (arg).properties - .filter(prop => this.validateProperty(prop)) - .map(prop => (prop).initializer) - .forEach(i => { - const selectors: compiler.CssSelector[] = this.extractMainSelector(i); - if (!this.rule.validateType(selectors)) { - let error = sprintf(this.rule.getTypeFailure(), className, this.rule.getOptions().ruleArguments[0]); - this.addFailureAtNode(i, error); - } else if (!this.rule.validateStyle(selectors)) { - let name = this.rule.getOptions().ruleArguments[2]; - if (name === 'kebab-case') { - name += ' and include dash'; - } - let error = sprintf(this.rule.getStyleFailure(), className, name); - this.addFailureAtNode(i, error); - } else if (!this.rule.validatePrefix(selectors)) { - let error = sprintf(this.rule.getPrefixFailure(this.rule.prefixes), className, this.rule.prefixes.join(', ')); - this.addFailureAtNode(i, error); - } - }); + if (!ts.isObjectLiteralExpression(arg)) { + return; } - } - private validateProperty(p: any) { - return (p.name).text === 'selector' && p.initializer && this.isSupportedKind(p.initializer.kind); + arg.properties + .filter(prop => ts.isPropertyAssignment(prop) && this.validateProperty(prop)) + .map(prop => (ts.isPropertyAssignment(prop) ? prop.initializer : undefined)) + .filter(Boolean) + .forEach(i => { + const selectors = this.extractMainSelector(i as ts.StringLiteral); + let error: string | undefined; + + if (!this.rule.validateType(selectors)) { + error = sprintf(this.rule.getTypeFailure(), className, this.rule.getOptions().ruleArguments[0]); + } else if (!this.rule.validateStyle(selectors)) { + let name = this.rule.getOptions().ruleArguments[2]; + if (name === 'kebab-case') { + name += ' and include dash'; + } + error = sprintf(this.rule.getStyleFailure(), className, name); + } else if (!this.rule.validatePrefix(selectors)) { + error = sprintf(this.rule.getPrefixFailure(this.rule.prefixes), className, this.rule.prefixes.join(', ')); + } + + if (error) { + this.addFailureAtNode(i!, error); + } + }); } - private isSupportedKind(kind: number): boolean { - return [ts.SyntaxKind.StringLiteral, ts.SyntaxKind.NoSubstitutionTemplateLiteral].some(kindType => kindType === kind); + private validateProperty(p: ts.PropertyAssignment): boolean { + return ts.isStringLiteralLike(p.initializer) && ts.isIdentifier(p.name) && p.name.text === 'selector'; } - private extractMainSelector(i: any) { - return compiler.CssSelector.parse(i.text); + private extractMainSelector(expression: ts.StringLiteral): compiler.CssSelector[] { + return compiler.CssSelector.parse(expression.text); } } diff --git a/src/templatesNoNegatedAsyncRule.ts b/src/templatesNoNegatedAsyncRule.ts index 403b1490e..bb8789b35 100644 --- a/src/templatesNoNegatedAsyncRule.ts +++ b/src/templatesNoNegatedAsyncRule.ts @@ -53,7 +53,7 @@ class TemplateToNgTemplateVisitor extends RecursiveAngularExpressionVisitor { ]); } - protected isAsyncBinding(expr: any) { + protected isAsyncBinding(expr: ast.AST) { return expr instanceof ast.BindingPipe && expr.name === 'async'; } } diff --git a/src/trackByFunctionRule.ts b/src/trackByFunctionRule.ts index c4d2fdc40..bc56c1a12 100644 --- a/src/trackByFunctionRule.ts +++ b/src/trackByFunctionRule.ts @@ -27,12 +27,12 @@ export const getFailureMessage = (): string => { }; class TrackByFunctionTemplateVisitor extends BasicTemplateAstVisitor { - visitDirectiveProperty(prop: BoundDirectivePropertyAst, context: any): any { + visitDirectiveProperty(prop: BoundDirectivePropertyAst, context: BasicTemplateAstVisitor): any { this.validateDirective(prop, context); super.visitDirectiveProperty(prop, context); } - private validateDirective(prop: BoundDirectivePropertyAst, context: any): any { + private validateDirective(prop: BoundDirectivePropertyAst, context: BasicTemplateAstVisitor): any { const { templateName } = prop; if (templateName !== 'ngForOf') { @@ -41,7 +41,7 @@ class TrackByFunctionTemplateVisitor extends BasicTemplateAstVisitor { const pattern = /trackBy\s*:|\[ngForTrackBy\]\s*=\s*['"].*['"]/; - if (pattern.test(context.codeWithMap.source)) { + if (pattern.test(context.codeWithMap.source!)) { return; } @@ -60,7 +60,7 @@ class TrackByTemplateVisitor extends BasicTemplateAstVisitor { new TrackByFunctionTemplateVisitor(this.getSourceFile(), this.getOptions(), this.context, this.templateStart) ]); - visitDirectiveProperty(prop: BoundDirectivePropertyAst, context: any): any { + visitDirectiveProperty(prop: BoundDirectivePropertyAst, context: BasicTemplateAstVisitor): any { this.visitors.forEach(visitor => visitor.visitDirectiveProperty(prop, this)); super.visitDirectiveProperty(prop, context); diff --git a/src/useLifeCycleInterfaceRule.ts b/src/useLifeCycleInterfaceRule.ts index 51ca62b9f..97369ab44 100644 --- a/src/useLifeCycleInterfaceRule.ts +++ b/src/useLifeCycleInterfaceRule.ts @@ -1,7 +1,7 @@ import { sprintf } from 'sprintf-js'; import * as Lint from 'tslint'; import * as ts from 'typescript'; -import { getInterfaceName } from './util/utils'; +import { getSymbolName } from './util/utils'; export class Rule extends Lint.Rules.AbstractRule { static readonly metadata: Lint.IRuleMetadata = { @@ -37,7 +37,7 @@ export class ClassMetadataWalker extends Lint.RuleWalker { visitClassDeclaration(node: ts.ClassDeclaration) { const className = node.name!.text; const interfaces = this.extractInterfaces(node); - const methods = node.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration); + const methods = node.members.filter(ts.isMethodDeclaration); this.validateMethods(methods, interfaces, className); super.visitClassDeclaration(node); @@ -48,7 +48,7 @@ export class ClassMetadataWalker extends Lint.RuleWalker { if (node.heritageClauses) { const interfacesClause = node.heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword); if (interfacesClause.length !== 0) { - interfaces = interfacesClause[0].types.map(getInterfaceName); + interfaces = interfacesClause[0].types.map(getSymbolName); } } return interfaces; diff --git a/src/usePipeDecoratorRule.ts b/src/usePipeDecoratorRule.ts index 738ccc338..52cea156e 100644 --- a/src/usePipeDecoratorRule.ts +++ b/src/usePipeDecoratorRule.ts @@ -1,7 +1,7 @@ import { sprintf } from 'sprintf-js'; import { IRuleMetadata, RuleFailure, Rules, RuleWalker } from 'tslint/lib'; import { ClassDeclaration, SourceFile, SyntaxKind } from 'typescript/lib/typescript'; -import { getDecoratorName, getInterfaceName } from './util/utils'; +import { getDecoratorName, getSymbolName } from './util/utils'; export class Rule extends Rules.AbstractRule { static readonly metadata: IRuleMetadata = { @@ -35,7 +35,7 @@ const hasPipeTransform = (node: ClassDeclaration): boolean => { const interfacesClauses = heritageClauses.filter(h => h.token === SyntaxKind.ImplementsKeyword); - return interfacesClauses.length > 0 && interfacesClauses[0].types.map(getInterfaceName).indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; + return interfacesClauses.length > 0 && interfacesClauses[0].types.map(getSymbolName).indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; }; export class ClassMetadataWalker extends RuleWalker { diff --git a/src/usePipeTransformInterfaceRule.ts b/src/usePipeTransformInterfaceRule.ts index 3fbfb4dbc..15a0bc559 100644 --- a/src/usePipeTransformInterfaceRule.ts +++ b/src/usePipeTransformInterfaceRule.ts @@ -1,7 +1,7 @@ import { sprintf } from 'sprintf-js'; import { IRuleMetadata, RuleFailure, Rules, RuleWalker } from 'tslint/lib'; import { ClassDeclaration, SourceFile, SyntaxKind } from 'typescript/lib/typescript'; -import { getDecoratorName, getInterfaceName } from './util/utils'; +import { getDecoratorName, getSymbolName } from './util/utils'; export class Rule extends Rules.AbstractRule { static readonly metadata: IRuleMetadata = { @@ -35,7 +35,7 @@ const hasPipeTransform = (node: ClassDeclaration): boolean => { const interfacesClauses = heritageClauses.filter(h => h.token === SyntaxKind.ImplementsKeyword); - return interfacesClauses.length > 0 && interfacesClauses[0].types.map(getInterfaceName).indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; + return interfacesClauses.length > 0 && interfacesClauses[0].types.map(getSymbolName).indexOf(Rule.PIPE_INTERFACE_NAME) !== -1; }; export class ClassMetadataWalker extends RuleWalker { diff --git a/src/useViewEncapsulationRule.ts b/src/useViewEncapsulationRule.ts index 2fd6b41c5..7f138fcd5 100644 --- a/src/useViewEncapsulationRule.ts +++ b/src/useViewEncapsulationRule.ts @@ -1,5 +1,5 @@ import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib'; -import { SourceFile } from 'typescript/lib/typescript'; +import { isPropertyAccessExpression, SourceFile } from 'typescript/lib/typescript'; import { ComponentMetadata } from './angular/metadata'; import { NgWalker } from './angular/ngWalker'; import { getDecoratorPropertyInitializer } from './util/utils'; @@ -31,7 +31,7 @@ class ViewEncapsulationWalker extends NgWalker { private validateComponent(metadata: ComponentMetadata): void { const encapsulation = getDecoratorPropertyInitializer(metadata.decorator, 'encapsulation'); - if (!encapsulation || encapsulation.name.text !== 'None') { + if (!encapsulation || (isPropertyAccessExpression(encapsulation) && encapsulation.name.text !== 'None')) { return; } diff --git a/src/util/astQuery.ts b/src/util/astQuery.ts index 64e469ac3..d48418203 100644 --- a/src/util/astQuery.ts +++ b/src/util/astQuery.ts @@ -1,16 +1,9 @@ import * as ts from 'typescript'; import { Maybe, ifTrue } from './function'; +import { isSimpleTemplateString } from './utils'; 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 === ts.SyntaxKind.PropertyAssignment; -} - -export function isSimpleTemplateString(expr: ts.Expression): expr is ts.StringLiteral | ts.NoSubstitutionTemplateLiteral { - return (expr && expr.kind === ts.SyntaxKind.StringLiteral) || expr.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral; + return Maybe.lift(dec!.expression).fmap(expr => (expr && ts.isCallExpression(expr) ? expr : undefined)); } export function hasProperties(expr?: ts.ObjectLiteralExpression): boolean { @@ -18,35 +11,31 @@ export function hasProperties(expr?: ts.ObjectLiteralExpression): boolean { } export function objectLiteralExpression(expr?: ts.CallExpression): Maybe { - return Maybe.lift(expr!.arguments[0]).fmap( - arg0 => (ts.isObjectLiteralExpression(arg0!) ? (arg0 as ts.ObjectLiteralExpression) : undefined) - ); + return Maybe.lift(expr!.arguments[0]).fmap(arg0 => (arg0 && ts.isObjectLiteralExpression(arg0) ? arg0 : undefined)); } 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) && ts.isIdentifier(p.name) && p.name.text === propName; + return ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === propName; } export function getInitializer(p: ts.ObjectLiteralElement): Maybe { - return Maybe.lift(isPropertyAssignment(p) && ts.isIdentifier(p.name) ? p.initializer : undefined); + return Maybe.lift(p && ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) ? p.initializer : undefined); } export function getStringInitializerFromProperty( propertyName: string, ps: ts.NodeArray -): Maybe { +): 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)) + // A little wrinkle to return Maybe + .fmap(expr => (expr && isSimpleTemplateString(expr) ? (expr as ts.StringLiteralLike) : undefined)) ); } diff --git a/src/util/classDeclarationUtils.ts b/src/util/classDeclarationUtils.ts index 732c6af91..c6fbce41f 100644 --- a/src/util/classDeclarationUtils.ts +++ b/src/util/classDeclarationUtils.ts @@ -2,36 +2,30 @@ import * as ts from 'typescript'; import { FlatSymbolTable } from '../angular/templates/recursiveAngularExpressionVisitor'; export const getDeclaredProperties = (declaration: ts.ClassDeclaration) => { - const m = declaration.members; - 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 === ts.SyntaxKind.Parameter); - } - return m - .filter( - (m: any) => - m.kind === ts.SyntaxKind.PropertyDeclaration || m.kind === ts.SyntaxKind.GetAccessor || m.kind === ts.SyntaxKind.SetAccessor - ) - .concat(params); + const { members } = declaration; + const ctr = members.find(ts.isConstructorDeclaration); + const params = (ctr ? ctr.parameters.filter(ts.isParameter) : []) as any; + + return members.filter(x => ts.isPropertyDeclaration(x) || ts.isGetAccessor(x) || ts.isSetAccessor(x)).concat(params); }; export const getDeclaredPropertyNames = (declaration: ts.ClassDeclaration) => { return getDeclaredProperties(declaration) - .filter((p: any) => p && p.name) - .reduce((accum: FlatSymbolTable, p: any) => { - accum[p.name.text] = true; + .filter(p => p.name && ts.isIdentifier(p.name)) + .map(p => (p.name as ts.Identifier).text) + .reduce((accum, p) => { + accum[p] = true; return accum; }, {}); }; export const getDeclaredMethods = (declaration: ts.ClassDeclaration) => { - return declaration.members.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration); + return declaration.members.filter(ts.isMethodDeclaration); }; export const getDeclaredMethodNames = (declaration: ts.ClassDeclaration) => { return getDeclaredMethods(declaration) - .map(d => (d.name).text) + .map(d => (d.name as ts.Identifier).text) .reduce((accum, m) => { accum[m] = true; return accum; diff --git a/src/util/ngQuery.ts b/src/util/ngQuery.ts index 56aa6111b..14d9d78ac 100644 --- a/src/util/ngQuery.ts +++ b/src/util/ngQuery.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import { decoratorArgument, getInitializer, getStringInitializerFromProperty, isProperty, WithStringInitializer } from './astQuery'; +import { decoratorArgument, getInitializer, getStringInitializerFromProperty, isProperty } from './astQuery'; import { Maybe } from './function'; export function getAnimations(dec: ts.Decorator): Maybe { @@ -14,14 +14,14 @@ export function getInlineStyle(dec: ts.Decorator): Maybe { const property = expr!.properties.find(p => isProperty('styles', p))!; - return getInitializer(property).fmap(expr => (ts.isArrayLiteralExpression(expr!) ? (expr as ts.ArrayLiteralExpression) : undefined)); + return getInitializer(property).fmap(expr => (expr && ts.isArrayLiteralExpression(expr) ? expr : undefined)); }); } -export function getTemplate(dec: ts.Decorator): Maybe { +export function getTemplate(dec: ts.Decorator): Maybe { return decoratorArgument(dec).bind(expr => getStringInitializerFromProperty('template', expr!.properties)); } -export function getTemplateUrl(dec: ts.Decorator): Maybe { +export function getTemplateUrl(dec: ts.Decorator): Maybe { return decoratorArgument(dec).bind(expr => getStringInitializerFromProperty('templateUrl', expr!.properties)); } diff --git a/src/util/utils.ts b/src/util/utils.ts index a5b148a04..8d47bdf6d 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -1,37 +1,38 @@ import * as ts from 'typescript'; -const SyntaxKind = require('./syntaxKind'); -// Lewenshtein algorithm -export const stringDistance = (s: string, t: string, ls = s.length, lt = t.length) => { - let memo = {}; - let currRowMemo; - let i: number; - let k: number; - for (k = 0; k <= lt; k += 1) { - memo[k] = k; - } - for (i = 1; i <= ls; i += 1) { - currRowMemo = [i]; - for (k = 1; k <= lt; k += 1) { - currRowMemo[k] = Math.min(currRowMemo[k - 1] + 1, memo[k] + 1, memo[k - 1] + (s[i - 1] !== t[k - 1] ? 1 : 0)); +export const isSimpleTemplateString = (e: any): e is ts.SyntaxKind.FirstTemplateToken | ts.StringLiteralLike => { + return ts.isStringLiteralLike(e) || e.kind === ts.SyntaxKind.FirstTemplateToken; +}; + +export const getClassName = (property: ts.PropertyDeclaration): string | undefined => { + const { parent } = property; + const identifier = parent && ts.isClassDeclaration(parent) ? parent.name : undefined; + + return identifier && ts.isIdentifier(identifier) ? identifier.text : undefined; +}; + +export const getDecoratorArgument = (decorator: ts.Decorator): ts.ObjectLiteralExpression | undefined => { + const { expression } = decorator; + + if (ts.isCallExpression(expression) && expression.arguments && expression.arguments.length > 0) { + const args = expression.arguments[0]; + + if (ts.isObjectLiteralExpression(args) && args.properties) { + return args; } - memo = currRowMemo; } - return memo[lt]; -}; -export const isSimpleTemplateString = (e: any) => { - return e.kind === ts.SyntaxKind.StringLiteral || e.kind === SyntaxKind.current().FirstTemplateToken; + return undefined; }; export const getDecoratorPropertyInitializer = (decorator: ts.Decorator, name: string) => { - 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() - ); + const args = ts.isCallExpression(decorator.expression) ? decorator.expression.arguments[0] : undefined; + const properties = ts.createNodeArray(args && ts.isObjectLiteralExpression(args) ? args.properties : undefined); + + return properties + .filter(prop => prop.name && ts.isIdentifier(prop.name) && prop.name.text === name) + .map(prop => (ts.isPropertyAssignment(prop) ? prop.initializer : undefined)) + .pop(); }; export const getDecoratorName = (decorator: ts.Decorator): string | undefined => { @@ -41,32 +42,22 @@ export const getDecoratorName = (decorator: ts.Decorator): string | undefined => }; export const getComponentDecorator = (declaration: ts.ClassDeclaration) => { - return ([].slice.apply(declaration.decorators) || []) - .filter(d => { - if ( - !(d.expression).arguments || - !(d.expression).arguments.length || - !((d.expression).arguments[0]).properties - ) { - return false; - } - const name = getDecoratorName(d); - if (name === 'Component') { - return true; - } - }) - .pop(); + return ts.createNodeArray(declaration.decorators).find(d => { + return ( + ts.isCallExpression(d.expression) && + d.expression.arguments && + d.expression.arguments.length > 0 && + getDecoratorName(d) === 'Component' + ); + }); }; -export const getInterfaceName = (expression: ts.ExpressionWithTypeArguments): string => { +export const getSymbolName = (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 []; - } - return nodes; + return nodes || []; }; diff --git a/src/walkerFactory/walkerFactory.ts b/src/walkerFactory/walkerFactory.ts index fe133f25d..8dfd36790 100644 --- a/src/walkerFactory/walkerFactory.ts +++ b/src/walkerFactory/walkerFactory.ts @@ -32,9 +32,7 @@ class NgComponentWalkerBuilder implements WalkerBuilder<'NgComponent'> { const self = this; const e = class extends NgWalker { visitNgComponent(meta: ComponentMetadata) { - self._where(meta).fmap(failure => { - this.addFailureAtNode(failure!.node, failure!.message); - }); + self._where(meta).fmap(failure => this.addFailureAtNode(failure.node, failure.message)); super.visitNgComponent(meta); } }; diff --git a/src/walkerFactory/walkerFn.ts b/src/walkerFactory/walkerFn.ts index 0fd0dc527..2a8d45c0a 100644 --- a/src/walkerFactory/walkerFn.ts +++ b/src/walkerFactory/walkerFn.ts @@ -22,7 +22,7 @@ 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) }); } diff --git a/test/angular/basicCssAstVisitor.spec.ts b/test/angular/basicCssAstVisitor.spec.ts index 5f6613aeb..1d6f840ca 100644 --- a/test/angular/basicCssAstVisitor.spec.ts +++ b/test/angular/basicCssAstVisitor.spec.ts @@ -8,7 +8,7 @@ import * as spies from 'chai-spies'; chai.use(spies); -const chaiSpy = (chai).spy; +const chaiSpy = (chai as any).spy; describe('basicCssAstVisitor', () => { it('should use the default css walker by default', () => { @@ -28,11 +28,11 @@ describe('basicCssAstVisitor', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(BasicCssAstVisitor.prototype, 'visitCssStyleSheet'); walker.walk(sf); - (chai.expect(templateSpy).to.have.been).called(); + (chai.expect(templateSpy).to.have.been as any).called(); }) .not.to.throw(); }); @@ -57,13 +57,13 @@ describe('basicCssAstVisitor', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let selectorSpy = chaiSpy.on(BasicCssAstVisitor.prototype, 'visitCssSelector'); let pseudoSelectorSpy = chaiSpy.on(BasicCssAstVisitor.prototype, 'visitCssPseudoSelector'); walker.walk(sf); - (chai.expect(selectorSpy).to.have.been).called(); - (chai.expect(pseudoSelectorSpy).to.have.been).called(); + (chai.expect(selectorSpy).to.have.been as any).called(); + (chai.expect(pseudoSelectorSpy).to.have.been as any).called(); }) .not.to.throw(); }); diff --git a/test/angular/metadataReader.spec.ts b/test/angular/metadataReader.spec.ts index 6ff5b2e6d..80e4a1d8b 100644 --- a/test/angular/metadataReader.spec.ts +++ b/test/angular/metadataReader.spec.ts @@ -1,11 +1,11 @@ +import { expect } from 'chai'; import * as ts from 'typescript'; -import chai = require('chai'); +import { Config } from '../../src/angular/config'; import { DummyFileResolver } from '../../src/angular/fileResolver/dummyFileResolver'; import { FsFileResolver } from '../../src/angular/fileResolver/fsFileResolver'; -import { MetadataReader } from '../../src/angular/metadataReader'; import { ComponentMetadata } from '../../src/angular/metadata'; -import { Config } from '../../src/angular/config'; +import { MetadataReader } from '../../src/angular/metadataReader'; import { join, normalize } from 'path'; @@ -24,8 +24,8 @@ describe('metadataReader', () => { `; const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); - const metadata = reader.read(last(ast.statements))!; - chai.expect(metadata.selector).eq('foo'); + const metadata = reader.read(last(ast.statements) as ts.ClassDeclaration)!; + expect(metadata.selector).eq('foo'); }); it('should not fail with empty decorator', () => { @@ -35,8 +35,8 @@ describe('metadataReader', () => { `; const reader = new MetadataReader(new DummyFileResolver()); const ast = getAst(code); - const metadata = reader.read(last(ast.statements))!; - chai.expect(metadata.selector).eq(undefined); + const metadata = reader.read(last(ast.statements) as ts.ClassDeclaration)!; + expect(metadata.selector).eq(undefined); }); it('should provide class declaration', () => { @@ -48,7 +48,7 @@ describe('metadataReader', () => { const ast = getAst(code); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata.controller).eq(classDeclaration); + expect(metadata.controller).eq(classDeclaration); }); }); @@ -66,13 +66,13 @@ describe('metadataReader', () => { const ast = getAst(code); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('bar'); - chai.expect(m.template.url).eq(undefined); - chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); + expect(m.template!.template.code).eq('bar'); + expect(m.template!.url).eq(undefined); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); }); it('should work with external template', () => { @@ -88,13 +88,13 @@ describe('metadataReader', () => { const ast = getAst(code); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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(undefined); + expect(m.template!.template.code).eq(''); + expect(m.template!.url).eq('bar'); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); }); it('should work with ignore templateUrl when has template', () => { @@ -111,13 +111,13 @@ describe('metadataReader', () => { const ast = getAst(code); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('qux'); - chai.expect(m.template.url).eq(undefined); - chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); + expect(m.template!.template.code).eq('qux'); + expect(m.template!.url).eq(undefined); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); }); it('should work with relative paths', () => { @@ -133,13 +133,13 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); + expect(m.template!.template.code.trim()).eq('
'); + expect(m.template!.url!.endsWith('foo.html')).eq(true); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); }); it('should work with absolute paths', () => { @@ -155,13 +155,13 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); + expect(m.template!.template.code.trim()).eq('
'); + expect(m.template!.url!.endsWith('foo.html')).eq(true); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); }); it('should work invoke Config.resolveUrl after all resolves', () => { @@ -170,7 +170,7 @@ describe('metadataReader', () => { try { Config.resolveUrl = url => { invoked = true; - chai.expect(url!.startsWith(normalize(join(__dirname, '../..')))).eq(true); + expect(url!.startsWith(normalize(join(__dirname, '../..')))).eq(true); return url; }; const code = ` @@ -185,16 +185,16 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); - chai.expect(invoked).eq(false); + expect(invoked).eq(false); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); - chai.expect(invoked).eq(true); + expect(m.template!.template.code.trim()).eq('
'); + expect(m.template!.url!.endsWith('foo.html')).eq(true); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); + expect(invoked).eq(true); } finally { Config.resolveUrl = bak; } @@ -206,7 +206,7 @@ describe('metadataReader', () => { try { Config.transformTemplate = code => { invoked = true; - chai.expect(code.trim()).eq('
'); + expect(code.trim()).eq('
'); return { code }; }; const code = ` @@ -221,16 +221,16 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); - chai.expect(invoked).eq(false); + expect(invoked).eq(false); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); - chai.expect(invoked).eq(true); + expect(m.template!.template.code.trim()).eq('
'); + expect(m.template!.url!.endsWith('foo.html')).eq(true); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); + expect(invoked).eq(true); } finally { Config.transformTemplate = bak; } @@ -242,7 +242,7 @@ describe('metadataReader', () => { try { Config.transformStyle = code => { invoked = true; - chai.expect(code).eq('baz'); + expect(code).eq('baz'); return { code }; }; const code = ` @@ -257,16 +257,16 @@ describe('metadataReader', () => { const reader = new MetadataReader(new FsFileResolver()); const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); const classDeclaration = last(ast.statements); - chai.expect(invoked).eq(false); + expect(invoked).eq(false); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); - chai.expect(invoked).eq(true); + expect(m.template!.template.code.trim()).eq('
'); + expect(m.template!.url!.endsWith('foo.html')).eq(true); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); + expect(invoked).eq(true); } finally { Config.transformStyle = bak; } @@ -286,13 +286,13 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/specialsymbols/foo.ts'); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].url).eq(undefined); + expect(m.template!.template.code.trim()).eq('
`
'); + expect(m.template!.url!.endsWith('foo.html')).eq(true); + expect(m.styles![0]!.style.code).eq('baz'); + expect(m.styles![0]!.url).eq(undefined); }); it('should work work with templates with "`"', () => { @@ -309,13 +309,13 @@ describe('metadataReader', () => { const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/notsupported/foo.ts'); const classDeclaration = last(ast.statements); const metadata = reader.read(classDeclaration)!; - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); + expect(metadata instanceof ComponentMetadata).eq(true); + 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.styles[0].style.code).eq(''); - chai.expect(m.styles[0].url!.endsWith('foo.sty')).eq(true); + expect(m.template!.template.code.trim()).eq(''); + expect(m.template!.url!.endsWith('foo.dust')).eq(true); + expect(m.styles![0]!.style.code).eq(''); + 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 2d5c62850..0f45b88f6 100644 --- a/test/angular/ngWalker.spec.ts +++ b/test/angular/ngWalker.spec.ts @@ -10,7 +10,8 @@ import * as spies from 'chai-spies'; chai.use(spies); -const chaiSpy = (chai).spy; +const chaiSpy = (chai as any).spy; + describe('ngWalker', () => { it('should visit components, directives, pipes, injectables, and modules', () => { let source = ` @@ -46,11 +47,11 @@ describe('ngWalker', () => { let modSpy = chaiSpy.on(walker, 'visitNgModule'); let injSpy = chaiSpy.on(walker, 'visitNgInjectable'); walker.walk(sf); - (chai.expect(cmpSpy).to.have.been).called(); - (chai.expect(dirSpy).to.have.been).called(); - (chai.expect(pipeSpy).to.have.been).called(); - (chai.expect(modSpy).to.have.been).called(); - (chai.expect(injSpy).to.have.been).called(); + (chai.expect(cmpSpy).to.have.been as any).called(); + (chai.expect(dirSpy).to.have.been as any).called(); + (chai.expect(pipeSpy).to.have.been as any).called(); + (chai.expect(modSpy).to.have.been as any).called(); + (chai.expect(injSpy).to.have.been as any).called(); }); it('should visit inputs and outputs with args', () => { @@ -100,7 +101,7 @@ describe('ngWalker', () => { }); let templateSpy = chaiSpy.on(BasicTemplateAstVisitor.prototype, 'visitElement'); walker.walk(sf); - (chai.expect(templateSpy).to.have.been).called(); + (chai.expect(templateSpy).to.have.been as any).called(); }); it('should visit component template expressions', () => { @@ -121,7 +122,7 @@ describe('ngWalker', () => { let walker = new NgWalker(sf, ruleArgs); let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); - (chai.expect(templateSpy).to.have.been).called(); + (chai.expect(templateSpy).to.have.been as any).called(); }); it('should not thow when a template is not literal', () => { @@ -141,11 +142,11 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); - (chai.expect(templateSpy).to.not.have.been).called(); + (chai.expect(templateSpy).to.not.have.been as any).called(); }) .not.to.throw(); }); @@ -167,11 +168,11 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); - (chai.expect(templateSpy).to.not.have.been).called(); + (chai.expect(templateSpy).to.not.have.been as any).called(); }) .not.to.throw(); }); @@ -193,11 +194,11 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); - (chai.expect(templateSpy).to.not.have.been).called(); + (chai.expect(templateSpy).to.not.have.been as any).called(); }) .not.to.throw(); }); @@ -218,11 +219,11 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); - (chai.expect(templateSpy).to.not.have.been).called(); + (chai.expect(templateSpy).to.not.have.been as any).called(); }) .not.to.throw(); }); @@ -240,11 +241,11 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(RecursiveAngularExpressionVisitor.prototype, 'visitPropertyRead'); walker.walk(sf); - (chai.expect(templateSpy).to.not.have.been).called(); + (chai.expect(templateSpy).to.not.have.been as any).called(); }) .not.to.throw(); }); @@ -262,7 +263,7 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { walker.walk(sf); }) @@ -287,7 +288,7 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { walker.walk(sf); }) @@ -311,11 +312,11 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { let templateSpy = chaiSpy.on(BasicCssAstVisitor.prototype, 'visitCssStyleSheet'); walker.walk(sf); - (chai.expect(templateSpy).to.have.been).called(); + (chai.expect(templateSpy).to.have.been as any).called(); }) .not.to.throw(); }); @@ -357,7 +358,7 @@ describe('ngWalker', () => { }; let sf = ts.createSourceFile('foo', source, ts.ScriptTarget.ES5); let walker = new NgWalker(sf, ruleArgs); - (chai) + chai .expect(() => { walker.walk(sf); }) diff --git a/test/angular/sourceMappingVisitor.spec.ts b/test/angular/sourceMappingVisitor.spec.ts index 9d91046e5..c94ba628a 100644 --- a/test/angular/sourceMappingVisitor.spec.ts +++ b/test/angular/sourceMappingVisitor.spec.ts @@ -1,6 +1,6 @@ -import * as ts from 'typescript'; -import chai = require('chai'); +import { expect } from 'chai'; import { renderSync } from 'node-sass'; +import * as ts from 'typescript'; import { SourceMappingVisitor } from '../../src/angular/sourceMappingVisitor'; import { getDecoratorPropertyInitializer } from '../../src/util/utils'; @@ -28,10 +28,10 @@ const last = (nodes: ts.NodeArray) => nodes[nodes.length - describe('SourceMappingVisitor', () => { it('should map to correct position', () => { const ast = getAst(fixture1); - const classDeclaration = last(ast.statements); + const classDeclaration = last(ast.statements); const styles = getDecoratorPropertyInitializer(last(classDeclaration.decorators!), 'styles'); - const styleNode = styles.elements[0]; - const scss = (styleNode).text; + const styleNode = (styles as ts.ArrayLiteralExpression).elements[0]; + const scss = (styleNode as any).text; const result = renderSync({ outFile: '/tmp/bar', data: scss, sourceMap: true }); const visitor = new SourceMappingVisitor( ast, @@ -50,7 +50,7 @@ describe('SourceMappingVisitor', () => { ); visitor.addFailureAt(0, 3, 'bar'); const failure = visitor.getFailures()[0]; - chai.expect(failure.getStartPosition().getPosition()).eq(34); - chai.expect(failure.getEndPosition().getPosition()).eq(38); + expect(failure.getStartPosition().getPosition()).eq(34); + expect(failure.getEndPosition().getPosition()).eq(38); }); }); diff --git a/test/angular/urlResolvers/urlResolver.spec.ts b/test/angular/urlResolvers/urlResolver.spec.ts index 1ed9b9d30..ee166ba06 100644 --- a/test/angular/urlResolvers/urlResolver.spec.ts +++ b/test/angular/urlResolvers/urlResolver.spec.ts @@ -1,5 +1,5 @@ +import { expect } from 'chai'; import * as ts from 'typescript'; -import chai = require('chai'); import { AbstractResolver } from '../../../src/angular/urlResolvers/abstractResolver'; @@ -35,7 +35,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const template = resolver.getTemplate(last(ast.statements).decorators![0]); - (chai).expect(template).eq('./foo/bar'); + expect(template).eq('./foo/bar'); }); it('should be able to resolve templateUrls set with template string', () => { @@ -48,7 +48,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const template = resolver.getTemplate(last(ast.statements).decorators![0]); - (chai).expect(template).eq('./foo/bar'); + expect(template).eq('./foo/bar'); }); it('should not be able to resolve templateUrls set with complex template string', () => { @@ -61,7 +61,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const template = resolver.getTemplate(last(ast.statements).decorators![0]); - (chai).expect(template).eq(undefined); + expect(template).eq(undefined); }); it('should not be able to resolve missing templateUrl', () => { @@ -74,7 +74,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const template = resolver.getTemplate(last(ast.statements).decorators![0]); - (chai).expect(template).eq(undefined); + expect(template).eq(undefined); }); it('should not be able to resolve templateUrls when having missing object literal', () => { @@ -85,7 +85,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const template = resolver.getTemplate(last(ast.statements).decorators![0]); - (chai).expect(template).eq(null); + expect(template).eq(undefined); }); it('should not be able to resolve templateUrls when having missing object literal', () => { @@ -96,7 +96,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const template = resolver.getTemplate(last(ast.statements).decorators![0]); - (chai).expect(template).eq(null); + expect(template).eq(undefined); }); }); @@ -109,7 +109,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const styles = resolver.getStyles(last(ast.statements).decorators![0]); - chai.expect(styles).to.deep.equal([]); + expect(styles).to.deep.equal([]); }); it('should not be able to resolve styleUrls when having missing object literal', () => { @@ -120,7 +120,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const styles = resolver.getStyles(last(ast.statements).decorators![0]); - chai.expect(styles).to.deep.equal([]); + expect(styles).to.deep.equal([]); }); it('should be able to resolve styleUrls with string literal', () => { @@ -136,7 +136,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const styles = resolver.getStyles(last(ast.statements).decorators![0]); - chai.expect(styles).to.deep.equal(['./foo', './bar']); + expect(styles).to.deep.equal(['./foo', './bar']); }); it('should be able to resolve styleUrls with string literal', () => { @@ -152,7 +152,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const styles = resolver.getStyles(last(ast.statements).decorators![0]); - chai.expect(styles).to.deep.equal(['./foo', './bar']); + expect(styles).to.deep.equal(['./foo', './bar']); }); it('should ignore non-string literal urls', () => { @@ -170,7 +170,7 @@ describe('urlResolver', () => { const ast = getAst(source); const resolver = new DummyResolver(); const styles = resolver.getStyles(last(ast.statements).decorators![0]); - chai.expect(styles).to.deep.equal(['./foo', './bar']); + expect(styles).to.deep.equal(['./foo', './bar']); }); }); }); diff --git a/test/componentClassSuffixRule.spec.ts b/test/componentClassSuffixRule.spec.ts index 7ce5edcf7..8c63d3618 100644 --- a/test/componentClassSuffixRule.spec.ts +++ b/test/componentClassSuffixRule.spec.ts @@ -95,7 +95,7 @@ describe('component-class-suffix', () => { assertSuccess('component-class-suffix', source, ['Page', 'View']); }); - it('should fail when different list of suffix is set and doesnt match', function() { + it('should fail when different list of suffix is set and doesnt match', () => { let source = ` @Component({ selector: 'sgBarFoo' diff --git a/test/noUnusedCssRule.spec.ts b/test/noUnusedCssRule.spec.ts index dcb85fe29..5f28fbdbb 100644 --- a/test/noUnusedCssRule.spec.ts +++ b/test/noUnusedCssRule.spec.ts @@ -925,7 +925,7 @@ describe('no-unused-css', () => { }); describe('invalid CSS', () => { - it('should work with non-matching quotes', function() { + it('should work with non-matching quotes', () => { let source = ` @Component({ selector: 'div[some-component]', @@ -946,7 +946,7 @@ describe('no-unused-css', () => { assertSuccess('no-unused-css', source); }); - it('should work with invalid CSS', function() { + it('should work with invalid CSS', () => { let source = ` @Component({ selector: 'div[some-component]', diff --git a/test/testHelper.ts b/test/testHelper.ts index 546789c92..64eec4907 100644 --- a/test/testHelper.ts +++ b/test/testHelper.ts @@ -1,4 +1,4 @@ -import chai = require('chai'); +import { assert } from 'chai'; import { ILinterOptions, IOptions, Linter, LintResult, RuleFailure } from 'tslint/lib'; import { SourceFile } from 'typescript/lib/typescript'; import { loadRules, convertRuleOptions } from './utils'; @@ -236,13 +236,13 @@ export function assertFailure( try { result = lint(ruleName, source, options); - chai.assert(result.failures && result.failures.length > 0, 'no failures'); + 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"); + assert.equal(fail.message, ruleFail.getFailure(), "error messages don't match"); + assert.deepEqual(fail.startPosition, ruleFail.getStartPosition().getLineAndCharacter(), "start char doesn't match"); + assert.deepEqual(fail.endPosition, ruleFail.getEndPosition().getLineAndCharacter(), "end char doesn't match"); return result ? result.failures : undefined; } catch (e) { @@ -268,11 +268,11 @@ export function assertFailures(ruleName: string, source: string | SourceFile, fa console.log(e.stack); } - chai.assert(result.failures && result.failures.length > 0, 'no failures'); + 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"); - chai.assert.deepEqual(fails[index].startPosition, ruleFail.getStartPosition().getLineAndCharacter(), "start char doesn't match"); - chai.assert.deepEqual(fails[index].endPosition, ruleFail.getEndPosition().getLineAndCharacter(), "end char doesn't match"); + assert.equal(fails[index].message, ruleFail.getFailure(), "error messages don't match"); + assert.deepEqual(fails[index].startPosition, ruleFail.getStartPosition().getLineAndCharacter(), "start char doesn't match"); + assert.deepEqual(fails[index].endPosition, ruleFail.getEndPosition().getLineAndCharacter(), "end char doesn't match"); }); } @@ -285,5 +285,5 @@ export function assertFailures(ruleName: string, source: string | SourceFile, fa */ export function assertSuccess(ruleName: string, source: string | SourceFile, options?: any) { const result = lint(ruleName, source, options); - chai.assert.isTrue(result && result.failures.length === 0); + assert.isTrue(result && result.failures.length === 0); } diff --git a/test/util/classDeclarationUtils.spec.ts b/test/util/classDeclarationUtils.spec.ts index 9ad177072..7295430a2 100644 --- a/test/util/classDeclarationUtils.spec.ts +++ b/test/util/classDeclarationUtils.spec.ts @@ -1,10 +1,10 @@ +import { expect } from 'chai'; import * as ts from 'typescript'; import * as Lint from 'tslint'; import { NgWalker } from '../../src/angular/ngWalker'; import { getDeclaredMethodNames, getDeclaredPropertyNames } from '../../src/util/classDeclarationUtils'; import { FlatSymbolTable } from '../../src/angular/templates/recursiveAngularExpressionVisitor'; -import chai = require('chai'); describe('ngWalker', () => { it('should visit components and directives', () => { @@ -34,7 +34,7 @@ describe('ngWalker', () => { 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 }); - chai.expect(properties).to.deep.eq({ foo: true }); + expect(methods).to.deep.eq({ bar: true, baz: true }); + expect(properties).to.deep.eq({ foo: true }); }); });