diff --git a/build/links.ts b/build/links.ts deleted file mode 100644 index 57bc59beb..000000000 --- a/build/links.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {processFiles} from './processFiles'; - -const LINKS = require('../../style-guide-links/all.json'); -const ARGS = require('minimist')(process.argv.slice(2)); -const SRC = ARGS.src; - -try { - processFiles(SRC, { - regexp: /\$\$(\d\d-\d\d)\$\$/g, - replace: (substring: string, code: string) => { - const link = LINKS[code]; - if (link === undefined) { - throw new Error('Non existing link for style #' + code); - } - return link; - } - }); -} catch (e) { - console.error(e); -} diff --git a/package.json b/package.json index 2ec154961..275747ef3 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "name": "codelyzer", - "version": "3.2.1", + "version": "4.0.0", "description": "Linting for Angular applications, following angular.io/styleguide.", "main": "index.js", "scripts": { "docs": "ts-node build/buildDocs.ts", "lint": "tslint -c tslint.json \"src/**/*.ts\" \"test/**/*.ts\"", "lint:fix": "npm run lint -- --fix", - "release": "npm run build && rimraf dist && tsc -p tsconfig-release.json && npm run copy:common && npm run prepare:package && BUILD_TYPE=prod npm run set:vars && npm run build:links", + "release": + "npm run build && rimraf dist && tsc -p tsconfig-release.json && npm run copy:common && npm run prepare:package && BUILD_TYPE=prod npm run set:vars", "build": "rimraf dist && tsc && npm run lint && npm t", "copy:common": "cp README.md dist", - "build:links": "ts-node build/links.ts --src ./dist", "prepare:package": "cat package.json | ts-node build/package.ts > dist/package.json", "test": "rimraf dist && tsc && cp -r test/fixtures dist/test && mocha dist/test --recursive", - "test:watch": "rimraf dist && tsc && cp -r test/fixtures dist/test && BUILD_TYPE=dev npm run set:vars && mocha dist/test --watch --recursive", + "test:watch": + "rimraf dist && tsc && cp -r test/fixtures dist/test && BUILD_TYPE=dev npm run set:vars && mocha dist/test --watch --recursive", "set:vars": "ts-node build/vars.ts --src ./dist", "tscv": "tsc --version", "tsc": "tsc", @@ -28,15 +29,7 @@ "type": "git", "url": "git+https://github.com/mgechev/codelyzer.git" }, - "keywords": [ - "Angular", - "style guide", - "styleguide", - "nglint", - "codelyzer", - "lint", - "tslint" - ], + "keywords": ["Angular", "style guide", "styleguide", "nglint", "codelyzer", "lint", "tslint"], "author": { "name": "Minko Gechev", "email": "mgechev@gmail.com" @@ -47,8 +40,11 @@ }, "homepage": "https://github.com/mgechev/codelyzer#readme", "devDependencies": { - "@angular/compiler": "^4.4.0", - "@angular/core": "^4.4.0", + "@angular/compiler": "^5.0.0-rc.7", + "@angular/core": "^5.0.0-rc.7", + "@angular/platform-browser-dynamic": "^5.0.0-rc.7", + "@angular/common": "^5.0.0-rc.7", + "@angular/platform-browser": "^5.0.0-rc.7", "@types/chai": "^3.4.33", "@types/less": "0.0.31", "@types/mocha": "^2.2.32", @@ -62,7 +58,7 @@ "mocha": "3.0.2", "node-sass": "^3.13.0", "rimraf": "^2.5.2", - "rxjs": "5.4.1", + "rxjs": "^5.5.0", "ts-node": "1.2.2", "tslint": "^5.0.0", "typescript": "2.4.0", @@ -72,8 +68,11 @@ "@types/js-yaml": "^3.5.31" }, "peerDependencies": { - "@angular/compiler": "^2.3.1 || >=4.0.0-beta <5.0.0", - "@angular/core": "^2.3.1 || >=4.0.0-beta <5.0.0", + "@angular/compiler": "^2.3.1 || >=4.0.0-beta <6.0.0", + "@angular/core": "^2.3.1 || >=4.0.0-beta <6.0.0", + "@angular/platform-browser-dynamic": "^2.3.1 || >=4.0.0-beta <6.0.0", + "@angular/platform-browser": "^2.3.1 || >=4.0.0-beta <6.0.0", + "@angular/common": "^2.3.1 || >=4.0.0-beta <6.0.0", "tslint": "^5.0.0" }, "dependencies": { diff --git a/src/angular/templates/jitReflector.ts b/src/angular/templates/jitReflector.ts new file mode 100644 index 000000000..7e3c0f8d4 --- /dev/null +++ b/src/angular/templates/jitReflector.ts @@ -0,0 +1,138 @@ +import { CompileReflector, ExternalReference, Identifiers, getUrlScheme, syntaxError } from '@angular/compiler'; +import { + ANALYZE_FOR_ENTRY_COMPONENTS, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ComponentFactory, + ComponentFactoryResolver, + ComponentRef, + ElementRef, + Injector, + LOCALE_ID, + NgModuleFactory, + NgModuleRef, + QueryList, + Renderer, + SecurityContext, + TRANSLATIONS_FORMAT, + TemplateRef, + ViewContainerRef, + ViewEncapsulation, + ɵCodegenComponentFactoryResolver, + ɵEMPTY_ARRAY, + ɵEMPTY_MAP, + ɵReflectionCapabilities as ReflectionCapabilities, + ɵand, + ɵccf, + ɵcmf, + ɵcrt, + ɵdid, + ɵeld, + ɵinlineInterpolate, + ɵinterpolate, + ɵmod, + ɵmpd, + ɵncd, + ɵnov, + ɵpad, + ɵpid, + ɵpod, + ɵppd, + ɵprd, + ɵqud, + ɵregisterModuleFactory, + ɵstringify as stringify, + ɵted, + ɵunv, + ɵvid +} from '@angular/core'; + +export const MODULE_SUFFIX = ''; +const builtinExternalReferences = createBuiltinExternalReferencesMap(); + +export class JitReflector implements CompileReflector { + private reflectionCapabilities: ReflectionCapabilities; + private builtinExternalReferences = new Map(); + constructor() { + this.reflectionCapabilities = new ReflectionCapabilities(); + } + componentModuleUrl(type: any, cmpMetadata: Component): string { + const moduleId = cmpMetadata.moduleId; + + if (typeof moduleId === 'string') { + const scheme = getUrlScheme(moduleId); + return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`; + } else if (moduleId !== null && moduleId !== void 0) { + throw syntaxError( + `moduleId should be a string in "${stringify(type)}". See https://goo.gl/wIDDiL for more information.\n` + + `If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.` + ); + } + + return `./${stringify(type)}`; + } + parameters(typeOrFunc: /*Type*/ any): any[][] { + return this.reflectionCapabilities.parameters(typeOrFunc); + } + annotations(typeOrFunc: /*Type*/ any): any[] { + return this.reflectionCapabilities.annotations(typeOrFunc); + } + propMetadata(typeOrFunc: /*Type*/ any): { [key: string]: any[] } { + return this.reflectionCapabilities.propMetadata(typeOrFunc); + } + hasLifecycleHook(type: any, lcProperty: string): boolean { + return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty); + } + resolveExternalReference(ref: ExternalReference): any { + return builtinExternalReferences.get(ref) || ref.runtime; + } +} + +function createBuiltinExternalReferencesMap() { + const map = new Map(); + map.set(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS, ANALYZE_FOR_ENTRY_COMPONENTS); + map.set(Identifiers.ElementRef, ElementRef); + map.set(Identifiers.NgModuleRef, NgModuleRef); + map.set(Identifiers.ViewContainerRef, ViewContainerRef); + map.set(Identifiers.ChangeDetectorRef, ChangeDetectorRef); + map.set(Identifiers.QueryList, QueryList); + map.set(Identifiers.TemplateRef, TemplateRef); + map.set(Identifiers.CodegenComponentFactoryResolver, ɵCodegenComponentFactoryResolver); + map.set(Identifiers.ComponentFactoryResolver, ComponentFactoryResolver); + map.set(Identifiers.ComponentFactory, ComponentFactory); + map.set(Identifiers.ComponentRef, ComponentRef); + map.set(Identifiers.NgModuleFactory, NgModuleFactory); + map.set(Identifiers.createModuleFactory, ɵcmf); + map.set(Identifiers.moduleDef, ɵmod); + map.set(Identifiers.moduleProviderDef, ɵmpd); + map.set(Identifiers.RegisterModuleFactoryFn, ɵregisterModuleFactory); + map.set(Identifiers.Injector, Injector); + map.set(Identifiers.ViewEncapsulation, ViewEncapsulation); + map.set(Identifiers.ChangeDetectionStrategy, ChangeDetectionStrategy); + map.set(Identifiers.SecurityContext, SecurityContext); + map.set(Identifiers.LOCALE_ID, LOCALE_ID); + map.set(Identifiers.TRANSLATIONS_FORMAT, TRANSLATIONS_FORMAT); + map.set(Identifiers.inlineInterpolate, ɵinlineInterpolate); + map.set(Identifiers.interpolate, ɵinterpolate); + map.set(Identifiers.EMPTY_ARRAY, ɵEMPTY_ARRAY); + map.set(Identifiers.EMPTY_MAP, ɵEMPTY_MAP); + map.set(Identifiers.Renderer, Renderer); + map.set(Identifiers.viewDef, ɵvid); + map.set(Identifiers.elementDef, ɵeld); + map.set(Identifiers.anchorDef, ɵand); + map.set(Identifiers.textDef, ɵted); + map.set(Identifiers.directiveDef, ɵdid); + map.set(Identifiers.providerDef, ɵprd); + map.set(Identifiers.queryDef, ɵqud); + map.set(Identifiers.pureArrayDef, ɵpad); + map.set(Identifiers.pureObjectDef, ɵpod); + map.set(Identifiers.purePipeDef, ɵppd); + map.set(Identifiers.pipeDef, ɵpid); + map.set(Identifiers.nodeValue, ɵnov); + map.set(Identifiers.ngContentDef, ɵncd); + map.set(Identifiers.unwrapValue, ɵunv); + map.set(Identifiers.createRendererType2, ɵcrt); + map.set(Identifiers.createComponentFactory, ɵccf); + return map; +} diff --git a/src/angular/templates/templateParser.ts b/src/angular/templates/templateParser.ts index ba2b3cc19..805c09b72 100644 --- a/src/angular/templates/templateParser.ts +++ b/src/angular/templates/templateParser.ts @@ -47,10 +47,7 @@ class Console { let defaultDirectives = []; -export const parseTemplate = ( - template: string, - directives: DirectiveDeclaration[] = [] -) => { +export const parseTemplate = (template: string, directives: DirectiveDeclaration[] = []) => { defaultDirectives = directives.map(d => dummyMetadataFactory(d)); const TemplateParser = compiler.TemplateParser; @@ -63,17 +60,16 @@ export const parseTemplate = ( SemVerDSL.gte('4.0.0-beta.8', () => { const config = new compiler.CompilerConfig({}); - tmplParser = new TemplateParser( - config, - expressionParser, - elementSchemaRegistry, - htmlParser, - ngConsole, - [] - ); + tmplParser = new TemplateParser(config, expressionParser, elementSchemaRegistry, htmlParser, ngConsole, []); }) .elseIf.lt('4.1.0', () => { + tmplParser = new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, ngConsole, []); + }) + .elseIf.lt('5.0.0-rc.0', () => { + const config = new compiler.CompilerConfig({}); tmplParser = new TemplateParser( + config, + new (compiler as any).JitReflector(), expressionParser, elementSchemaRegistry, htmlParser, @@ -82,13 +78,14 @@ export const parseTemplate = ( ); }) .else(() => { + const JitReflector = require('./jitReflector').JitReflector; const config = new compiler.CompilerConfig({}); - tmplParser = new TemplateParser( + tmplParser = new compiler.TemplateParser( config, - new compiler.JitReflector(), + new JitReflector(), expressionParser, elementSchemaRegistry, - htmlParser, + htmlParser as any, ngConsole, [] ); @@ -178,35 +175,35 @@ export const parseTemplate = ( '' ).templateAst; }) - .elseIf.lt('4.4.0', () => { - result = tmplParser.tryParse( - compiler.CompileDirectiveMetadata.create({ - type, - template: templateMetadata, - isHost: true, - isComponent: true, - selector: '', - exportAs: '', - changeDetection: ChangeDetectionStrategy.Default, - inputs: [], - outputs: [], - host: {}, - providers: [], - viewProviders: [], - queries: [], - viewQueries: [], - entryComponents: [], - componentViewType: null, - rendererType: null, - componentFactory: null - }), - template, - defaultDirectives, - [], - [NO_ERRORS_SCHEMA], - '' - ).templateAst; - }) + .elseIf.lt('5.0.0-rc.0', () => { + result = tmplParser.tryParse( + compiler.CompileDirectiveMetadata.create({ + type, + template: templateMetadata, + isHost: true, + isComponent: true, + selector: '', + exportAs: '', + changeDetection: ChangeDetectionStrategy.Default, + inputs: [], + outputs: [], + host: {}, + providers: [], + viewProviders: [], + queries: [], + viewQueries: [], + entryComponents: [], + componentViewType: null, + rendererType: null, + componentFactory: null + }), + template, + defaultDirectives, + [], + [NO_ERRORS_SCHEMA], + '' + ).templateAst; + }) .else(() => { result = tmplParser.tryParse( compiler.CompileDirectiveMetadata.create({ @@ -233,7 +230,8 @@ export const parseTemplate = ( defaultDirectives, [], [NO_ERRORS_SCHEMA], - '', true + '', + true ).templateAst; }); } catch (e) { diff --git a/src/i18nRule.ts b/src/i18nRule.ts index b0067e66c..eb8d30a63 100644 --- a/src/i18nRule.ts +++ b/src/i18nRule.ts @@ -10,8 +10,7 @@ interface ConfigurableVisitor { getOption(): Option; } -class I18NAttrVisitor extends BasicTemplateAstVisitor - implements ConfigurableVisitor { +class I18NAttrVisitor extends BasicTemplateAstVisitor implements ConfigurableVisitor { visitAttr(attr: ast.AttrAst, context: BasicTemplateAstVisitor) { if (attr.name === 'i18n') { const parts = (attr.value || '').split('@@'); @@ -33,8 +32,7 @@ class I18NAttrVisitor extends BasicTemplateAstVisitor } } -class I18NTextVisitor extends BasicTemplateAstVisitor - implements ConfigurableVisitor { +class I18NTextVisitor extends BasicTemplateAstVisitor implements ConfigurableVisitor { static Error = 'Each element containing text node should have an i18n attribute'; private hasI18n = false; @@ -51,11 +49,7 @@ class I18NTextVisitor extends BasicTemplateAstVisitor ) { const span = text.sourceSpan; context.addFailure( - context.createFailure( - span.start.offset, - span.end.offset - span.start.offset, - I18NTextVisitor.Error - ) + context.createFailure(span.start.offset, span.end.offset - span.start.offset, I18NTextVisitor.Error) ); } } @@ -66,19 +60,12 @@ class I18NTextVisitor extends BasicTemplateAstVisitor if (!this.visited.has(text)) { this.visited.add(text); const val = text.value; - if ( - val instanceof ast.ASTWithSource && - val.ast instanceof ast.Interpolation - ) { + if (val instanceof ast.ASTWithSource && val.ast instanceof ast.Interpolation) { const textNonEmpty = val.ast.strings.some(s => /\w+/.test(s)); if (textNonEmpty) { const span = text.sourceSpan; context.addFailure( - context.createFailure( - span.start.offset, - span.end.offset - span.start.offset, - I18NTextVisitor.Error - ) + context.createFailure(span.start.offset, span.end.offset - span.start.offset, I18NTextVisitor.Error) ); } } @@ -100,18 +87,8 @@ class I18NTextVisitor extends BasicTemplateAstVisitor class I18NTemplateVisitor extends BasicTemplateAstVisitor { private visitors: (BasicTemplateAstVisitor & ConfigurableVisitor)[] = [ - new I18NAttrVisitor( - this.getSourceFile(), - this.getOptions(), - this.context, - this.templateStart - ), - new I18NTextVisitor( - this.getSourceFile(), - this.getOptions(), - this.context, - this.templateStart - ) + new I18NAttrVisitor(this.getSourceFile(), this.getOptions(), this.context, this.templateStart), + new I18NTextVisitor(this.getSourceFile(), this.getOptions(), this.context, this.templateStart) ]; visit(a: any, context: any) { diff --git a/src/index.ts b/src/index.ts index 202c95813..ada87ecfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,31 +1,29 @@ -export {Rule as AngularWhitespaceRule} from './angularWhitespaceRule'; -export {Rule as BananaInBoxRule} from './bananaInBoxRule'; -export {Rule as ComponentClassSuffixRule} from './componentClassSuffixRule'; -export {Rule as ComponentSelectorRule} from './componentSelectorRule'; -export {Rule as ContextualLifeCycleRule} from './contextualLifeCycleRule'; -export {Rule as DecoratorNotAllowedRule} from './decoratorNotAllowedRule'; -export {Rule as DirectiveClassSuffixRule} from './directiveClassSuffixRule'; -export {Rule as DirectiveSelectorRule} from './directiveSelectorRule'; -export {Rule as I18nRule} from './i18nRule'; -export {Rule as ImportDestructuringSpacingRule} from './importDestructuringSpacingRule'; -export {Rule as InvokeInjectableRule} from './invokeInjectableRule'; -export {Rule as NoAccessMissingMemberRule} from './noAccessMissingMemberRule'; -export {Rule as NoAttributeParameterDecoratorRule} from './noAttributeParameterDecoratorRule'; -export {Rule as NoForwardRefRule} from './noForwardRefRule'; -export {Rule as NoInputRenameRule} from './noInputRenameRule'; -export {Rule as NoOutputRenameRule} from './noOutputRenameRule'; -export {Rule as NoUnusedCssRule} from './noUnusedCssRule'; -export {Rule as PipeImpureRule} from './pipeImpureRule'; -export {Rule as PipeNamingRule} from './pipeNamingRule'; -export {Rule as TemplatesUsePublicRule} from './templatesUsePublicRule'; -export {Rule as UseHostPropertyDecoratorRule} from './useHostPropertyDecoratorRule'; -export {Rule as UseInputPropertyDecoratorRule} from './useInputPropertyDecoratorRule'; -export {Rule as UseLifeCycleInterfaceRule} from './useLifeCycleInterfaceRule'; -export {Rule as UseOutputPropertyDecoratorRule} from './useOutputPropertyDecoratorRule'; -export {Rule as UsePipeTransformInterfaceRule} from './usePipeTransformInterfaceRule'; -export {Rule as TemplateToNgTemplateRule} from './templateToNgTemplateRule'; -export {Rule as UsePipeDecoratorRule} from './usePipeDecoratorRule'; -export {Rule as UseViewEncapsulationRule} from './useViewEncapsulationRule'; -export {Rule as TemplatesNoNegatedAsync} from './templatesNoNegatedAsyncRule'; +export { Rule as AngularWhitespaceRule } from './angularWhitespaceRule'; +export { Rule as BananaInBoxRule } from './bananaInBoxRule'; +export { Rule as ComponentClassSuffixRule } from './componentClassSuffixRule'; +export { Rule as ComponentSelectorRule } from './componentSelectorRule'; +export { Rule as ContextualLifeCycleRule } from './contextualLifeCycleRule'; +export { Rule as DecoratorNotAllowedRule } from './decoratorNotAllowedRule'; +export { Rule as DirectiveClassSuffixRule } from './directiveClassSuffixRule'; +export { Rule as DirectiveSelectorRule } from './directiveSelectorRule'; +export { Rule as I18nRule } from './i18nRule'; +export { Rule as ImportDestructuringSpacingRule } from './importDestructuringSpacingRule'; +export { Rule as InvokeInjectableRule } from './invokeInjectableRule'; +export { Rule as NoAccessMissingMemberRule } from './noAccessMissingMemberRule'; +export { Rule as NoAttributeParameterDecoratorRule } from './noAttributeParameterDecoratorRule'; +export { Rule as NoForwardRefRule } from './noForwardRefRule'; +export { Rule as NoInputRenameRule } from './noInputRenameRule'; +export { Rule as NoOutputRenameRule } from './noOutputRenameRule'; +export { Rule as NoUnusedCssRule } from './noUnusedCssRule'; +export { Rule as PipeImpureRule } from './pipeImpureRule'; +export { Rule as PipeNamingRule } from './pipeNamingRule'; +export { Rule as TemplatesUsePublicRule } from './templatesUsePublicRule'; +export { Rule as UseHostPropertyDecoratorRule } from './useHostPropertyDecoratorRule'; +export { Rule as UseInputPropertyDecoratorRule } from './useInputPropertyDecoratorRule'; +export { Rule as UseLifeCycleInterfaceRule } from './useLifeCycleInterfaceRule'; +export { Rule as UseOutputPropertyDecoratorRule } from './useOutputPropertyDecoratorRule'; +export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule'; +export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule'; +export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule'; +export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule'; export * from './angular/config'; - diff --git a/src/templateToNgTemplateRule.ts b/src/templateToNgTemplateRule.ts deleted file mode 100644 index a1d17ec43..000000000 --- a/src/templateToNgTemplateRule.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as Lint from 'tslint'; -import * as ts from 'typescript'; -import { NgWalker } from './angular/ngWalker'; -import { EmbeddedTemplateAst } from '@angular/compiler'; -import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor'; - -const ErrorMessage = 'You should use instead of