From 599f0cdcdf30c1efbc9dee5f68a4a17e00181c9f Mon Sep 17 00:00:00 2001 From: mgechev Date: Wed, 23 Nov 2016 20:46:44 -0800 Subject: [PATCH 01/14] feat: add hooks --- package.json | 2 ++ src/angular/config.ts | 31 +++++++++++++++++++ src/angular/metadata.ts | 5 +-- src/angular/metadataReader.ts | 16 +++++++--- src/angular/ng2Walker.ts | 8 ++--- src/angular/styles/basicCssAstVisitor.ts | 5 +-- .../templates/basicTemplateAstVisitor.ts | 7 +++-- .../recursiveAngularExpressionVisitor.ts | 3 +- src/noAccessMissingMemberRule.ts | 4 +-- src/noUnusedCssRule.ts | 2 +- src/templatesUsePublicRule.ts | 2 +- 11 files changed, 64 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index d09c18746..7d495449e 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@types/chai": "^3.4.33", "@types/mocha": "^2.2.32", "@types/node": "^6.0.41", + "@types/source-map": "^0.5.0", "@types/sprintf-js": "0.0.27", "chai": "^3.5.0", "chai-spies": "^0.7.1", @@ -69,6 +70,7 @@ "app-root-path": "^2.0.1", "css-selector-tokenizer": "^0.7.0", "cssauron": "^1.4.0", + "source-map": "^0.5.6", "sprintf-js": "^1.0.3" } } diff --git a/src/angular/config.ts b/src/angular/config.ts index 59021300b..c56ec5a92 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -1,4 +1,5 @@ import * as ts from 'typescript'; +import {CodeWithSourceMap} from 'source-map'; const root = require('app-root-path'); const join = require('path').join; @@ -7,9 +8,19 @@ export interface UrlResolver { (url: string, d: ts.Decorator): string; } +export interface TemplateTransformer { + (template: string, url: string, d: ts.Decorator): CodeWithSourceMap; +} + +export interface StyleTransformer { + (style: string, url: string, d: ts.Decorator): CodeWithSourceMap; +} + export interface Config { interpolation: [string, string]; resolveUrl: UrlResolver; + transformTemplate: TemplateTransformer; + transformStyle: StyleTransformer; predefinedDirectives: DirectiveDeclaration[]; basePath: string; } @@ -21,9 +32,29 @@ export interface DirectiveDeclaration { export let Config: Config = { interpolation: ['{{', '}}'], + resolveUrl(url: string, d: ts.Decorator) { return url; }, + + transformTemplate(template: string, url: string, d: ts.Decorator) { + if (!url || url.endsWith('.html')) { + return { + code: template, + map: null + }; + } + }, + + transformStyle(style: string, url: string, d: ts.Decorator) { + if (!url || url.endsWith('.css')) { + return { + code: style, + map: null + }; + } + }, + predefinedDirectives: [ { selector: 'form', exportAs: 'ngForm' } ], diff --git a/src/angular/metadata.ts b/src/angular/metadata.ts index e7c2b432b..e24021665 100644 --- a/src/angular/metadata.ts +++ b/src/angular/metadata.ts @@ -1,13 +1,14 @@ import * as ts from 'typescript'; +import {CodeWithSourceMap} from 'source-map'; export interface TemplateMetadata { - template: string; + template: CodeWithSourceMap; node: ts.Node; source: string; } export interface StyleMetadata { - style: string; + style: CodeWithSourceMap; node: ts.Node; source: string; } diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index 62ff44e9c..f51110dfd 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -4,6 +4,7 @@ import {isSimpleTemplateString, getDecoratorPropertyInitializer} from '../util/u const kinds = current(); +import {Config} from './config'; import {FileResolver} from './fileResolver/fileResolver'; import {AbstractResolver} from './urlResolvers/abstractResolver'; import {UrlResolver} from './urlResolvers/urlResolver'; @@ -73,8 +74,9 @@ export class MetadataReader { const inlineTemplate = getDecoratorPropertyInitializer(dec, 'template'); const external = this._urlResolver.resolve(dec); if (inlineTemplate && isSimpleTemplateString(inlineTemplate)) { + const transformed = Config.transformTemplate(inlineTemplate.text, null, dec); result.template = { - template: inlineTemplate.text, + template: transformed, source: null, node: inlineTemplate }; @@ -85,17 +87,19 @@ export class MetadataReader { if (isSimpleTemplateString(inlineStyle)) { result.styles = result.styles || []; result.styles.push({ - style: inlineStyle.text, + style: Config.transformStyle(inlineStyle.text, null, dec), source: null, - node: inlineStyle + node: inlineStyle, }); } }); } if (!result.template && external.templateUrl) { try { + const template = this._fileResolver.resolve(external.templateUrl); + const transformed = Config.transformTemplate(template, external.templateUrl, dec); result.template = { - template: this._fileResolver.resolve(external.templateUrl), + template: transformed, source: external.templateUrl, node: null }; @@ -107,8 +111,10 @@ export class MetadataReader { if (!result.styles || !result.styles.length) { try { result.styles = external.styleUrls.map((url: string) => { + const style = this._fileResolver.resolve(url); + const transformed = Config.transformStyle(style, url, dec); return { - style: this._fileResolver.resolve(url), + style: transformed, source: url, node: null }; diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts index 71642db6d..dbc53afc2 100644 --- a/src/angular/ng2Walker.ts +++ b/src/angular/ng2Walker.ts @@ -121,7 +121,7 @@ export class Ng2Walker extends Lint.RuleWalker { const template = metadata.template; if (template && template.template) { try { - const templateAst = parseTemplate(template.template, Config.predefinedDirectives); + const templateAst = parseTemplate(template.template.code, Config.predefinedDirectives); this.visitNg2TemplateHelper(templateAst, metadata, template.node ? template.node.pos + 2 : 0); } catch (e) { console.log(e); @@ -133,7 +133,7 @@ export class Ng2Walker extends Lint.RuleWalker { for (let i = 0; i < styles.length; i += 1) { const style = styles[i]; try { - this.visitNg2StyleHelper(parseCss(style.style), metadata, style.source, style.node ? style.node.pos + 2 : 0); + this.visitNg2StyleHelper(parseCss(style.style.code), metadata, style.source, style.node ? style.node.pos + 2 : 0); } catch (e) { console.log('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text); } @@ -164,7 +164,7 @@ export class Ng2Walker extends Lint.RuleWalker { } const visitor = new this._config.templateVisitorCtrl( - sourceFile, this._originalOptions, context.controller, baseStart, this._config.expressionVisitorCtrl); + sourceFile, this._originalOptions, context, baseStart, this._config.expressionVisitorCtrl); compiler.templateVisitAll(visitor, roots, context.controller); sourceFile.fileName = filename; visitor.getFailures().forEach(f => this.addFailure(f)); @@ -180,7 +180,7 @@ export class Ng2Walker extends Lint.RuleWalker { if (file) { sourceFile.fileName = context.template.source; } - const visitor = new this._config.cssVisitorCtrl(this.getSourceFile(), this._originalOptions, context.controller, baseStart); + const visitor = new this._config.cssVisitorCtrl(this.getSourceFile(), this._originalOptions, context, baseStart); style.visit(visitor); sourceFile.fileName = filename; visitor.getFailures().forEach(f => this.addFailure(f)); diff --git a/src/angular/styles/basicCssAstVisitor.ts b/src/angular/styles/basicCssAstVisitor.ts index f2a28530d..3a0338c21 100644 --- a/src/angular/styles/basicCssAstVisitor.ts +++ b/src/angular/styles/basicCssAstVisitor.ts @@ -1,15 +1,16 @@ import * as ast from './cssAst'; import * as ts from 'typescript'; import * as Lint from 'tslint'; +import {ComponentMetadata} from '../metadata'; export interface CssAstVisitorCtrl { - new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ts.ClassDeclaration, templateStart: number); + new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, templateStart: number); } export class BasicCssAstVisitor extends Lint.RuleWalker implements ast.CssAstVisitor { constructor(sourceFile: ts.SourceFile, protected _originalOptions: Lint.IOptions, - protected context: ts.ClassDeclaration, + protected context: ComponentMetadata, protected templateStart: number) { super(sourceFile, _originalOptions); } diff --git a/src/angular/templates/basicTemplateAstVisitor.ts b/src/angular/templates/basicTemplateAstVisitor.ts index 5ef6a5a02..98ed07ad1 100644 --- a/src/angular/templates/basicTemplateAstVisitor.ts +++ b/src/angular/templates/basicTemplateAstVisitor.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import * as e from '@angular/compiler/src/expression_parser/ast'; import { ExpTypes } from '../expressionTypes'; +import { ComponentMetadata } from '../metadata'; import { RecursiveAngularExpressionVisitor } from './recursiveAngularExpressionVisitor'; @@ -62,12 +63,12 @@ const getExpressionDisplacement = (binding: any) => { export interface RecursiveAngularExpressionVisitorCtr { - new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ts.ClassDeclaration, basePosition: number); + new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, basePosition: number); } export interface TemplateAstVisitorCtr { - new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ts.ClassDeclaration, + new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, templateStart: number, expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr); } @@ -76,7 +77,7 @@ export class BasicTemplateAstVisitor extends Lint.RuleWalker implements ast.Temp constructor(sourceFile: ts.SourceFile, private _originalOptions: Lint.IOptions, - private context: ts.ClassDeclaration, + private context: ComponentMetadata, protected templateStart: number, private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor) { super(sourceFile, _originalOptions); diff --git a/src/angular/templates/recursiveAngularExpressionVisitor.ts b/src/angular/templates/recursiveAngularExpressionVisitor.ts index 87bfc82cf..eb04336cd 100644 --- a/src/angular/templates/recursiveAngularExpressionVisitor.ts +++ b/src/angular/templates/recursiveAngularExpressionVisitor.ts @@ -1,12 +1,13 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import * as e from '@angular/compiler/src/expression_parser/ast'; +import {ComponentMetadata} from '../metadata'; export class RecursiveAngularExpressionVisitor extends Lint.RuleWalker implements e.AstVisitor { public preDefinedVariables = []; constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, - protected context: ts.ClassDeclaration, protected basePosition: number) { + protected context: ComponentMetadata, protected basePosition: number) { super(sourceFile, options); } diff --git a/src/noAccessMissingMemberRule.ts b/src/noAccessMissingMemberRule.ts index c3890653b..b285b2212 100644 --- a/src/noAccessMissingMemberRule.ts +++ b/src/noAccessMissingMemberRule.ts @@ -35,8 +35,8 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor { } else { symbolType = 'property'; } - available = getDeclaredMethodNames(this.context) - .concat(getDeclaredPropertyNames(this.context)) + available = getDeclaredMethodNames(this.context.controller) + .concat(getDeclaredPropertyNames(this.context.controller)) .concat(this.preDefinedVariables); ast.receiver.visit(this); diff --git a/src/noUnusedCssRule.ts b/src/noUnusedCssRule.ts index 75c8ddbeb..2ffa38d87 100644 --- a/src/noUnusedCssRule.ts +++ b/src/noUnusedCssRule.ts @@ -226,7 +226,7 @@ export class UnusedCssNg2Visitor extends Ng2Walker { if (!style) { return; } else { - const visitor = new UnusedCssVisitor(this.getSourceFile(), this._originalOptions, context.controller, baseStart); + const visitor = new UnusedCssVisitor(this.getSourceFile(), this._originalOptions, context, baseStart); visitor.templateAst = this.templateAst; const d = getComponentDecorator(context.controller); const encapsulation = getDecoratorPropertyInitializer(d, 'encapsulation'); diff --git a/src/templatesUsePublicRule.ts b/src/templatesUsePublicRule.ts index 77786d702..2feecc070 100644 --- a/src/templatesUsePublicRule.ts +++ b/src/templatesUsePublicRule.ts @@ -34,7 +34,7 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor { } ast = receiver; } - const allMembers = getDeclaredMethods(this.context).concat(getDeclaredProperties(this.context)); + const allMembers = getDeclaredMethods(this.context.controller).concat(getDeclaredProperties(this.context.controller)); const member = allMembers.filter((m: any) => m.name && m.name.text === ast.name).pop(); if (member) { let isPublic = !member.modifiers; From ccfe199e772e0bc24feae1afb740b22be61158c5 Mon Sep 17 00:00:00 2001 From: mgechev Date: Wed, 23 Nov 2016 21:47:16 -0800 Subject: [PATCH 02/14] fix(tests): assert with proper value --- src/angular/config.ts | 20 +++++++--------- src/angular/urlResolvers/abstractResolver.ts | 1 - test/angular/metadataReader.spec.ts | 25 ++++++++++---------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/angular/config.ts b/src/angular/config.ts index c56ec5a92..caaea0bba 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -38,21 +38,17 @@ export let Config: Config = { }, transformTemplate(template: string, url: string, d: ts.Decorator) { - if (!url || url.endsWith('.html')) { - return { - code: template, - map: null - }; - } + return { + code: template, + map: null + }; }, transformStyle(style: string, url: string, d: ts.Decorator) { - if (!url || url.endsWith('.css')) { - return { - code: style, - map: null - }; - } + return { + code: style, + map: null + }; }, predefinedDirectives: [ diff --git a/src/angular/urlResolvers/abstractResolver.ts b/src/angular/urlResolvers/abstractResolver.ts index f251e816d..b2641c1ea 100644 --- a/src/angular/urlResolvers/abstractResolver.ts +++ b/src/angular/urlResolvers/abstractResolver.ts @@ -65,4 +65,3 @@ export abstract class AbstractResolver { return null; } } - diff --git a/test/angular/metadataReader.spec.ts b/test/angular/metadataReader.spec.ts index 51513a10b..b27b6cf50 100644 --- a/test/angular/metadataReader.spec.ts +++ b/test/angular/metadataReader.spec.ts @@ -72,9 +72,9 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template).eq('bar'); + chai.expect(m.template.template.code).eq('bar'); chai.expect(m.template.source).eq(null); - chai.expect(m.styles[0].style).eq('baz'); + chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].source).eq(null); }); @@ -94,9 +94,9 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template).eq(''); + chai.expect(m.template.template.code).eq(''); chai.expect(m.template.source).eq('bar'); - chai.expect(m.styles[0].style).eq('baz'); + chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].source).eq(null); }); @@ -117,9 +117,9 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template).eq('qux'); + chai.expect(m.template.template.code).eq('qux'); chai.expect(m.template.source).eq(null); - chai.expect(m.styles[0].style).eq('baz'); + chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].source).eq(null); }); @@ -141,9 +141,9 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template).eq('
\n'); + chai.expect(m.template.template.code).eq('
\n'); chai.expect(m.template.source.endsWith('foo.html')).eq(true); - chai.expect(m.styles[0].style).eq('baz'); + chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].source).eq(null); }); @@ -164,13 +164,12 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template).eq('
\n'); + chai.expect(m.template.template.code).eq('
\n'); chai.expect(m.template.source.endsWith('foo.html')).eq(true); - chai.expect(m.styles[0].style).eq('baz'); + chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].source).eq(null); }); - it('should work invoke Config.resolveUrl after all resolves', () => { let invoked = false; Config.resolveUrl = (url: string) => { @@ -195,9 +194,9 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template).eq('
\n'); + chai.expect(m.template.template.code).eq('
\n'); chai.expect(m.template.source.endsWith('foo.html')).eq(true); - chai.expect(m.styles[0].style).eq('baz'); + chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].source).eq(null); chai.expect(invoked).eq(true); Config.resolveUrl = (url: string) => url; From 56bc9c94e7db09136a50baa8de49277bdb8d2cc8 Mon Sep 17 00:00:00 2001 From: mgechev Date: Thu, 24 Nov 2016 20:01:59 -0800 Subject: [PATCH 03/14] feat: add sourcemap support --- package.json | 4 ++ src/angular/config.ts | 16 ++--- src/angular/metadata.ts | 12 +++- src/angular/metadataReader.ts | 29 +++++--- src/angular/ng2Walker.ts | 18 ++--- src/angular/sourceMappingVisitor.ts | 55 ++++++++++++++++ src/angular/styles/basicCssAstVisitor.ts | 10 +-- .../templates/basicTemplateAstVisitor.ts | 7 +- .../recursiveAngularExpressionVisitor.ts | 5 +- src/noUnusedCssRule.ts | 19 +++--- test/angular/metadataReader.spec.ts | 24 +++---- test/noUnusedCssRule.spec.ts | 66 +++++++++++++++++-- 12 files changed, 193 insertions(+), 72 deletions(-) create mode 100644 src/angular/sourceMappingVisitor.ts diff --git a/package.json b/package.json index 7d495449e..9c51066c1 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,10 @@ "@angular/compiler": "^2.2.0", "@angular/core": "^2.2.0", "@types/chai": "^3.4.33", + "@types/less": "0.0.31", "@types/mocha": "^2.2.32", "@types/node": "^6.0.41", + "@types/node-sass": "^3.10.31", "@types/source-map": "^0.5.0", "@types/sprintf-js": "0.0.27", "chai": "^3.5.0", @@ -70,6 +72,8 @@ "app-root-path": "^2.0.1", "css-selector-tokenizer": "^0.7.0", "cssauron": "^1.4.0", + "less": "^2.7.1", + "node-sass": "^3.13.0", "source-map": "^0.5.6", "sprintf-js": "^1.0.3" } diff --git a/src/angular/config.ts b/src/angular/config.ts index caaea0bba..ba67de68e 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -1,5 +1,5 @@ import * as ts from 'typescript'; -import {CodeWithSourceMap} from 'source-map'; +import {CodeWithSourceMap} from './metadata'; const root = require('app-root-path'); const join = require('path').join; @@ -37,18 +37,12 @@ export let Config: Config = { return url; }, - transformTemplate(template: string, url: string, d: ts.Decorator) { - return { - code: template, - map: null - }; + transformTemplate(code: string, url: string, d: ts.Decorator) { + return { code }; }, - transformStyle(style: string, url: string, d: ts.Decorator) { - return { - code: style, - map: null - }; + transformStyle(code: string, url: string, d: ts.Decorator) { + return { code }; }, predefinedDirectives: [ diff --git a/src/angular/metadata.ts b/src/angular/metadata.ts index e24021665..f71150a87 100644 --- a/src/angular/metadata.ts +++ b/src/angular/metadata.ts @@ -1,16 +1,22 @@ import * as ts from 'typescript'; -import {CodeWithSourceMap} from 'source-map'; +import {RawSourceMap} from 'source-map'; + +export interface CodeWithSourceMap { + code: string; + source?: string; + map?: RawSourceMap; +} export interface TemplateMetadata { template: CodeWithSourceMap; node: ts.Node; - source: string; + url: string; } export interface StyleMetadata { style: CodeWithSourceMap; node: ts.Node; - source: string; + url: string; } export interface StylesMetadata { diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index f51110dfd..31bacb6b4 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -9,8 +9,18 @@ import {FileResolver} from './fileResolver/fileResolver'; import {AbstractResolver} from './urlResolvers/abstractResolver'; import {UrlResolver} from './urlResolvers/urlResolver'; -import {DirectiveMetadata, ComponentMetadata, StylesMetadata} from './metadata'; +import {DirectiveMetadata, ComponentMetadata, StylesMetadata, CodeWithSourceMap} from './metadata'; +const normalizeTransformed = (t: CodeWithSourceMap) => { + if (!t.map) { + t.source = t.code; + } + return t; +}; + +/** + * For async implementation https://gist.github.com/mgechev/6f2245c0dfb38539cc606ea9211ecb37 + */ export class MetadataReader { constructor(private _fileResolver: FileResolver, private _urlResolver?: AbstractResolver) { this._urlResolver = this._urlResolver || new UrlResolver(); @@ -74,10 +84,10 @@ export class MetadataReader { const inlineTemplate = getDecoratorPropertyInitializer(dec, 'template'); const external = this._urlResolver.resolve(dec); if (inlineTemplate && isSimpleTemplateString(inlineTemplate)) { - const transformed = Config.transformTemplate(inlineTemplate.text, null, dec); + const transformed = normalizeTransformed(Config.transformTemplate(inlineTemplate.text, null, dec)); result.template = { template: transformed, - source: null, + url: null, node: inlineTemplate }; } @@ -87,8 +97,8 @@ export class MetadataReader { if (isSimpleTemplateString(inlineStyle)) { result.styles = result.styles || []; result.styles.push({ - style: Config.transformStyle(inlineStyle.text, null, dec), - source: null, + style: normalizeTransformed(Config.transformStyle(inlineStyle.text, null, dec)), + url: null, node: inlineStyle, }); } @@ -97,10 +107,10 @@ export class MetadataReader { if (!result.template && external.templateUrl) { try { const template = this._fileResolver.resolve(external.templateUrl); - const transformed = Config.transformTemplate(template, external.templateUrl, dec); + const transformed = normalizeTransformed(Config.transformTemplate(template, external.templateUrl, dec)); result.template = { template: transformed, - source: external.templateUrl, + url: external.templateUrl, node: null }; } catch (e) { @@ -112,10 +122,9 @@ export class MetadataReader { try { result.styles = external.styleUrls.map((url: string) => { const style = this._fileResolver.resolve(url); - const transformed = Config.transformStyle(style, url, dec); + const transformed = normalizeTransformed(Config.transformStyle(style, url, dec)); return { - style: transformed, - source: url, + style: transformed, url, node: null }; }); diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts index dbc53afc2..e56679605 100644 --- a/src/angular/ng2Walker.ts +++ b/src/angular/ng2Walker.ts @@ -17,7 +17,7 @@ import {getDecoratorName, isSimpleTemplateString, getDecoratorPropertyInitialize import {MetadataReader} from './metadataReader'; import {ng2WalkerFactoryUtils} from './ng2WalkerFactoryUtils'; -import {ComponentMetadata, DirectiveMetadata} from './metadata'; +import {ComponentMetadata, DirectiveMetadata, StyleMetadata} from './metadata'; import {Config} from './config'; import SyntaxKind = require('../util/syntaxKind'); @@ -128,12 +128,12 @@ export class Ng2Walker extends Lint.RuleWalker { console.log('Cannot parse the template of', ((metadata.controller || {}).name || {}).text); } } - const styles = metadata.styles; + const styles = metadata.styles; if (styles && styles.length) { for (let i = 0; i < styles.length; i += 1) { const style = styles[i]; try { - this.visitNg2StyleHelper(parseCss(style.style.code), metadata, style.source, style.node ? style.node.pos + 2 : 0); + this.visitNg2StyleHelper(parseCss(style.style.code), metadata, style, style.node ? style.node.pos + 2 : 0); } catch (e) { console.log('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text); } @@ -159,8 +159,8 @@ export class Ng2Walker extends Lint.RuleWalker { } else { const sourceFile = this.getSourceFile(); let filename = sourceFile.fileName; - if (context.template.source) { - sourceFile.fileName = context.template.source; + if (context.template.url) { + sourceFile.fileName = context.template.url; } const visitor = new this._config.templateVisitorCtrl( @@ -171,16 +171,16 @@ export class Ng2Walker extends Lint.RuleWalker { } } - protected visitNg2StyleHelper(style: CssAst, context: ComponentMetadata, file: string, baseStart: number) { + protected visitNg2StyleHelper(style: CssAst, context: ComponentMetadata, styleMetadata: StyleMetadata, baseStart: number) { if (!style) { return; } else { const sourceFile = this.getSourceFile(); let filename = sourceFile.fileName; - if (file) { - sourceFile.fileName = context.template.source; + if (styleMetadata) { + sourceFile.fileName = styleMetadata.url; } - const visitor = new this._config.cssVisitorCtrl(this.getSourceFile(), this._originalOptions, context, baseStart); + const visitor = new this._config.cssVisitorCtrl(this.getSourceFile(), this._originalOptions, context, styleMetadata, baseStart); style.visit(visitor); sourceFile.fileName = filename; visitor.getFailures().forEach(f => this.addFailure(f)); diff --git a/src/angular/sourceMappingVisitor.ts b/src/angular/sourceMappingVisitor.ts new file mode 100644 index 000000000..b24dd383f --- /dev/null +++ b/src/angular/sourceMappingVisitor.ts @@ -0,0 +1,55 @@ +import * as ts from 'typescript'; +import {RuleWalker, IOptions} from 'tslint'; +import {ComponentMetadata, CodeWithSourceMap} from './metadata'; +import {SourceMapConsumer} from 'source-map'; + +const findLineAndColumnNumber = (pos: number, code: string) => { + code = code.replace('\r\n', '\n').replace('\r', '\n'); + let line = 1; + let column = 0; + for (let i = 0; i < pos; i += 1) { + column += 1; + if (code[i] === '\n') { + line += 1; + column = 0; + } + } + return { line, column }; +}; + +const findCharNumberFromLineAndColumn = ({ line, column }: { line: number, column: number }, code: string) => { + code = code.replace('\r\n', '\n').replace('\r', '\n'); + let char = 0; + while (line) { + if (code[char] === '\n') { + line -= 1; + } + char += 1; + } + return char + column; +}; + +export class SourceMappingVisitor extends RuleWalker { + + constructor(sourceFile: ts.SourceFile, options: IOptions, protected codeWithMap: CodeWithSourceMap, protected basePosition: number) { + super(sourceFile, options); + } + + createFailure(start: number, length: number, message: string) { + let end = start + length; + if (this.codeWithMap.map) { + const consumer = new SourceMapConsumer(this.codeWithMap.map); + start = this.getMappedPosition(start, consumer); + end = this.getMappedPosition(end, consumer); + } + return super.createFailure(start, end - start, message); + } + + private getMappedPosition(pos: number, consumer: SourceMapConsumer) { + const absPos = findLineAndColumnNumber(pos - this.basePosition, this.codeWithMap.code); + const mappedPos = consumer.originalPositionFor(absPos); + console.log(absPos, mappedPos); + const char = findCharNumberFromLineAndColumn(mappedPos, this.codeWithMap.source); + return char + this.basePosition; + } +} diff --git a/src/angular/styles/basicCssAstVisitor.ts b/src/angular/styles/basicCssAstVisitor.ts index 3a0338c21..efdde6edc 100644 --- a/src/angular/styles/basicCssAstVisitor.ts +++ b/src/angular/styles/basicCssAstVisitor.ts @@ -1,18 +1,20 @@ import * as ast from './cssAst'; import * as ts from 'typescript'; import * as Lint from 'tslint'; -import {ComponentMetadata} from '../metadata'; +import {SourceMappingVisitor} from '../sourceMappingVisitor'; +import {ComponentMetadata, StyleMetadata} from '../metadata'; export interface CssAstVisitorCtrl { - new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, templateStart: number); + new(sourceFile: ts.SourceFile, options: Lint.IOptions, context: ComponentMetadata, style: StyleMetadata, templateStart: number); } -export class BasicCssAstVisitor extends Lint.RuleWalker implements ast.CssAstVisitor { +export class BasicCssAstVisitor extends SourceMappingVisitor implements ast.CssAstVisitor { constructor(sourceFile: ts.SourceFile, protected _originalOptions: Lint.IOptions, protected context: ComponentMetadata, + style: StyleMetadata, protected templateStart: number) { - super(sourceFile, _originalOptions); + super(sourceFile, _originalOptions, style.style, templateStart); } visitCssValue(ast: ast.CssStyleValueAst, context?: any): any {} diff --git a/src/angular/templates/basicTemplateAstVisitor.ts b/src/angular/templates/basicTemplateAstVisitor.ts index 98ed07ad1..4eeb32ec2 100644 --- a/src/angular/templates/basicTemplateAstVisitor.ts +++ b/src/angular/templates/basicTemplateAstVisitor.ts @@ -6,6 +6,7 @@ import * as e from '@angular/compiler/src/expression_parser/ast'; import { ExpTypes } from '../expressionTypes'; import { ComponentMetadata } from '../metadata'; import { RecursiveAngularExpressionVisitor } from './recursiveAngularExpressionVisitor'; +import {SourceMappingVisitor} from '../sourceMappingVisitor'; const getExpressionDisplacement = (binding: any) => { @@ -72,15 +73,15 @@ export interface TemplateAstVisitorCtr { templateStart: number, expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr); } -export class BasicTemplateAstVisitor extends Lint.RuleWalker implements ast.TemplateAstVisitor { +export class BasicTemplateAstVisitor extends SourceMappingVisitor implements ast.TemplateAstVisitor { private _variables = []; constructor(sourceFile: ts.SourceFile, private _originalOptions: Lint.IOptions, - private context: ComponentMetadata, + protected context: ComponentMetadata, protected templateStart: number, private expressionVisitorCtrl: RecursiveAngularExpressionVisitorCtr = RecursiveAngularExpressionVisitor) { - super(sourceFile, _originalOptions); + super(sourceFile, _originalOptions, context.template.template, templateStart); } protected visitNg2TemplateAST(ast: e.AST, templateStart: number) { diff --git a/src/angular/templates/recursiveAngularExpressionVisitor.ts b/src/angular/templates/recursiveAngularExpressionVisitor.ts index eb04336cd..53cc20b81 100644 --- a/src/angular/templates/recursiveAngularExpressionVisitor.ts +++ b/src/angular/templates/recursiveAngularExpressionVisitor.ts @@ -1,14 +1,15 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import * as e from '@angular/compiler/src/expression_parser/ast'; +import {SourceMappingVisitor} from '../sourceMappingVisitor'; import {ComponentMetadata} from '../metadata'; -export class RecursiveAngularExpressionVisitor extends Lint.RuleWalker implements e.AstVisitor { +export class RecursiveAngularExpressionVisitor extends SourceMappingVisitor implements e.AstVisitor { public preDefinedVariables = []; constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, protected context: ComponentMetadata, protected basePosition: number) { - super(sourceFile, options); + super(sourceFile, options, context.template.template, basePosition); } visit(ast: e.AST, context: any) { diff --git a/src/noUnusedCssRule.ts b/src/noUnusedCssRule.ts index 2ffa38d87..a8b74c2be 100644 --- a/src/noUnusedCssRule.ts +++ b/src/noUnusedCssRule.ts @@ -12,7 +12,7 @@ import { import {parseTemplate} from './angular/templates/templateParser'; import {CssAst, CssSelectorRuleAst, CssSelectorAst} from './angular/styles/cssAst'; -import {ComponentMetadata} from './angular/metadata'; +import {ComponentMetadata, StyleMetadata} from './angular/metadata'; import {ng2WalkerFactoryUtils} from './angular/ng2WalkerFactoryUtils'; const CssSelectorTokenizer = require('css-selector-tokenizer'); @@ -205,15 +205,12 @@ export class UnusedCssNg2Visitor extends Ng2Walker { visitClassDeclaration(declaration: ts.ClassDeclaration) { const d = getComponentDecorator(declaration); if (d) { - const meta = this._metadataReader.read(declaration); - this.visitNg2Component(meta); - const inlineTemplate = getDecoratorPropertyInitializer(d, 'template'); - if (inlineTemplate) { + const meta: ComponentMetadata = this._metadataReader.read(declaration); + this.visitNg2Component(meta); + if (meta.template && meta.template.template) { try { - if (isSimpleTemplateString(inlineTemplate)) { - this.templateAst = - new ElementAst('*', [], [], [], [], [], [], false, parseTemplate(inlineTemplate.text), 0, null, null); - } + this.templateAst = + new ElementAst('*', [], [], [], [], [], [], false, parseTemplate(meta.template.template.code), 0, null, null); } catch (e) { console.error('Cannot parse the template', e); } @@ -222,11 +219,11 @@ export class UnusedCssNg2Visitor extends Ng2Walker { super.visitClassDeclaration(declaration); } - protected visitNg2StyleHelper(style: CssAst, context: ComponentMetadata, path: string, baseStart: number) { + protected visitNg2StyleHelper(style: CssAst, context: ComponentMetadata, styleMetadata: StyleMetadata, baseStart: number) { if (!style) { return; } else { - const visitor = new UnusedCssVisitor(this.getSourceFile(), this._originalOptions, context, baseStart); + const visitor = new UnusedCssVisitor(this.getSourceFile(), this._originalOptions, context, styleMetadata, baseStart); visitor.templateAst = this.templateAst; const d = getComponentDecorator(context.controller); const encapsulation = getDecoratorPropertyInitializer(d, 'encapsulation'); diff --git a/test/angular/metadataReader.spec.ts b/test/angular/metadataReader.spec.ts index b27b6cf50..a80db043d 100644 --- a/test/angular/metadataReader.spec.ts +++ b/test/angular/metadataReader.spec.ts @@ -73,9 +73,9 @@ describe('metadataReader', () => { chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('bar'); - chai.expect(m.template.source).eq(null); + chai.expect(m.template.url).eq(null); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].source).eq(null); + chai.expect(m.styles[0].url).eq(null); }); it('should work with external template', () => { @@ -95,9 +95,9 @@ describe('metadataReader', () => { chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq(''); - chai.expect(m.template.source).eq('bar'); + chai.expect(m.template.url).eq('bar'); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].source).eq(null); + chai.expect(m.styles[0].url).eq(null); }); it('should work with ignore templateUrl when has template', () => { @@ -118,9 +118,9 @@ describe('metadataReader', () => { chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('qux'); - chai.expect(m.template.source).eq(null); + chai.expect(m.template.url).eq(null); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].source).eq(null); + chai.expect(m.styles[0].url).eq(null); }); @@ -142,9 +142,9 @@ describe('metadataReader', () => { chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('
\n'); - chai.expect(m.template.source.endsWith('foo.html')).eq(true); + chai.expect(m.template.url.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].source).eq(null); + chai.expect(m.styles[0].url).eq(null); }); it('should work with absolute paths when module.id is not set', () => { @@ -165,9 +165,9 @@ describe('metadataReader', () => { chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('
\n'); - chai.expect(m.template.source.endsWith('foo.html')).eq(true); + chai.expect(m.template.url.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].source).eq(null); + chai.expect(m.styles[0].url).eq(null); }); it('should work invoke Config.resolveUrl after all resolves', () => { @@ -195,9 +195,9 @@ describe('metadataReader', () => { chai.expect(metadata.selector).eq('foo'); const m = metadata; chai.expect(m.template.template.code).eq('
\n'); - chai.expect(m.template.source.endsWith('foo.html')).eq(true); + chai.expect(m.template.url.endsWith('foo.html')).eq(true); chai.expect(m.styles[0].style.code).eq('baz'); - chai.expect(m.styles[0].source).eq(null); + chai.expect(m.styles[0].url).eq(null); chai.expect(invoked).eq(true); Config.resolveUrl = (url: string) => url; }); diff --git a/test/noUnusedCssRule.spec.ts b/test/noUnusedCssRule.spec.ts index 641de43ce..5776f83ed 100644 --- a/test/noUnusedCssRule.spec.ts +++ b/test/noUnusedCssRule.spec.ts @@ -1,7 +1,12 @@ +import {Decorator} from 'typescript'; + +import * as sass from 'node-sass'; + import {assertFailure, assertSuccess} from './testHelper'; +import {Config} from '../src/angular/config'; describe('no-unused-css', () => { - describe('valid cases', () => { + xdescribe('valid cases', () => { it('should succeed when having valid simple selector', () => { let source = ` @@ -199,7 +204,7 @@ describe('no-unused-css', () => { }); - describe('failures', () => { + xdescribe('failures', () => { it('should fail when having a complex selector that doesn\'t match anything', () => { let source = ` @Component({ @@ -341,7 +346,7 @@ describe('no-unused-css', () => { }); }); - describe('host', () => { + xdescribe('host', () => { it('should never fail for :host', () => { let source = ` @@ -400,7 +405,7 @@ describe('no-unused-css', () => { }); }); - describe('deep and >>>', () => { + xdescribe('deep and >>>', () => { it('should ignore deep and match only before it', () => { let source = ` @Component({ @@ -514,7 +519,7 @@ describe('no-unused-css', () => { }); }); - describe('pseudo', () => { + xdescribe('pseudo', () => { it('should ignore before and after', () => { let source = ` @@ -573,7 +578,7 @@ describe('no-unused-css', () => { }); }); - describe('ViewEncapsulation', () => { + xdescribe('ViewEncapsulation', () => { it('should ignore before and after', () => { let source = ` @Component({ @@ -696,7 +701,54 @@ describe('no-unused-css', () => { }); - describe('inconsistencies with template', () => { + + it('should work with sass', () => { + Config.transformStyle = (source: string, url: string, d: Decorator) => { + const res = sass.renderSync({ + sourceMap: true, data: source, sourceMapEmbed: true + }); + const code = res.css.toString(); + const base64Map = code.match(/\/\*(.*?)\*\//)[1].replace('# sourceMappingURL=data:application/json;base64,', ''); + const map = JSON.parse(new Buffer(base64Map, 'base64').toString('ascii')); + return { code, source, map }; + }; + + let source = ` + @Component({ + selector: 'hero-cmp', + template: \` +

Hello {{ hero.name }}

+ \`, + styles: [ + \` + h1 { + spam { + baz { + color: red; + } + } + } + \` + ] + }) + class HeroComponent { + private hero: Hero; + }`; + assertFailure('no-unused-css', source, { + message: 'Unused styles', + startPosition: { + line: 9, + character: 0 + }, + endPosition: { + line: 13, + character: 3 + } + }); + Config.transformStyle = (code: string) => ({ code, map: null }); + }); + + xdescribe('inconsistencies with template', () => { it('should ignore misspelled template', () => { let source = ` From 5cb2ea08c8deea773e4836f6419174dd313248f0 Mon Sep 17 00:00:00 2001 From: mgechev Date: Thu, 24 Nov 2016 23:24:57 -0800 Subject: [PATCH 04/14] refactoring --- src/angular/config.ts | 13 ++- src/angular/metadataReader.ts | 5 +- src/angular/ng2Walker.ts | 2 +- src/angular/ng2WalkerFactoryUtils.ts | 5 +- src/angular/sourceMappingVisitor.ts | 6 +- src/angular/urlResolvers/absoluteResolver.ts | 25 ----- src/angular/urlResolvers/abstractResolver.ts | 4 +- src/angular/urlResolvers/commonJsResolver.ts | 52 ----------- src/angular/urlResolvers/pathResolver.ts | 10 ++ src/angular/urlResolvers/urlResolver.ts | 53 +++++++---- src/angular/urlResolvers/webpackResolver.ts | 0 src/noAccessMissingMemberRule.ts | 2 +- src/noUnusedCssRule.ts | 2 +- src/templatesUsePublicRule.ts | 2 +- test/angular/metadataReader.spec.ts | 96 ++++++++++++++++++-- test/angular/sourceMappingVisitor.spec.ts | 48 ++++++++++ test/fixtures/metadataReader/sass/bar.scss | 20 ++++ test/noUnusedCssRule.spec.ts | 14 +-- 18 files changed, 236 insertions(+), 123 deletions(-) delete mode 100644 src/angular/urlResolvers/absoluteResolver.ts delete mode 100644 src/angular/urlResolvers/commonJsResolver.ts create mode 100644 src/angular/urlResolvers/pathResolver.ts delete mode 100644 src/angular/urlResolvers/webpackResolver.ts create mode 100644 test/angular/sourceMappingVisitor.spec.ts create mode 100644 test/fixtures/metadataReader/sass/bar.scss diff --git a/src/angular/config.ts b/src/angular/config.ts index ba67de68e..83356b29c 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -1,5 +1,6 @@ import * as ts from 'typescript'; import {CodeWithSourceMap} from './metadata'; +import {renderSync} from 'node-sass'; const root = require('app-root-path'); const join = require('path').join; @@ -22,7 +23,6 @@ export interface Config { transformTemplate: TemplateTransformer; transformStyle: StyleTransformer; predefinedDirectives: DirectiveDeclaration[]; - basePath: string; } export interface DirectiveDeclaration { @@ -42,13 +42,20 @@ export let Config: Config = { }, transformStyle(code: string, url: string, d: ts.Decorator) { + let result: CodeWithSourceMap = { source: code, code }; + if (url && (url.endsWith('.sass') || url.endsWith('.scss'))) { + // Note: it's not written on disk + // https://github.com/sass/node-sass#outfile + let res = renderSync({ file: url, data: code, sourceMap: true, outFile: url }); + result.map = JSON.parse(res.map.toString()); + result.code = res.css.toString(); + } return { code }; }, predefinedDirectives: [ { selector: 'form', exportAs: 'ngForm' } - ], - basePath: '' + ] }; try { diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index 31bacb6b4..56b4f8d40 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -8,6 +8,7 @@ import {Config} from './config'; import {FileResolver} from './fileResolver/fileResolver'; import {AbstractResolver} from './urlResolvers/abstractResolver'; import {UrlResolver} from './urlResolvers/urlResolver'; +import {PathResolver} from './urlResolvers/pathResolver'; import {DirectiveMetadata, ComponentMetadata, StylesMetadata, CodeWithSourceMap} from './metadata'; @@ -22,8 +23,9 @@ const normalizeTransformed = (t: CodeWithSourceMap) => { * For async implementation https://gist.github.com/mgechev/6f2245c0dfb38539cc606ea9211ecb37 */ export class MetadataReader { + constructor(private _fileResolver: FileResolver, private _urlResolver?: AbstractResolver) { - this._urlResolver = this._urlResolver || new UrlResolver(); + this._urlResolver = this._urlResolver || new UrlResolver(new PathResolver()); } read(d: ts.ClassDeclaration): DirectiveMetadata { @@ -146,4 +148,3 @@ export class MetadataReader { return null; } } - diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts index e56679605..f125e4d3b 100644 --- a/src/angular/ng2Walker.ts +++ b/src/angular/ng2Walker.ts @@ -128,7 +128,7 @@ export class Ng2Walker extends Lint.RuleWalker { console.log('Cannot parse the template of', ((metadata.controller || {}).name || {}).text); } } - const styles = metadata.styles; + const styles = metadata.styles; if (styles && styles.length) { for (let i = 0; i < styles.length; i += 1) { const style = styles[i]; diff --git a/src/angular/ng2WalkerFactoryUtils.ts b/src/angular/ng2WalkerFactoryUtils.ts index 3a7d2d733..2c4200f4f 100644 --- a/src/angular/ng2WalkerFactoryUtils.ts +++ b/src/angular/ng2WalkerFactoryUtils.ts @@ -5,10 +5,13 @@ import {MetadataReader} from './metadataReader'; import {UrlResolver} from './urlResolvers/urlResolver'; import {FsFileResolver} from './fileResolver/fsFileResolver'; import {BasicCssAstVisitor, CssAstVisitorCtrl} from './styles/basicCssAstVisitor'; +import {Config} from './config'; import { RecursiveAngularExpressionVisitor } from './templates/recursiveAngularExpressionVisitor'; import {BasicTemplateAstVisitor} from './templates/basicTemplateAstVisitor'; +import {PathResolver} from './urlResolvers/pathResolver'; + export const ng2WalkerFactoryUtils = { defaultConfig() { return { @@ -19,7 +22,7 @@ export const ng2WalkerFactoryUtils = { }, defaultMetadataReader() { - return new MetadataReader(new FsFileResolver(), new UrlResolver()); + return new MetadataReader(new FsFileResolver(), new UrlResolver(new PathResolver())); }, normalizeConfig(config: Ng2WalkerConfig) { diff --git a/src/angular/sourceMappingVisitor.ts b/src/angular/sourceMappingVisitor.ts index b24dd383f..1f1bcb6f8 100644 --- a/src/angular/sourceMappingVisitor.ts +++ b/src/angular/sourceMappingVisitor.ts @@ -41,14 +41,16 @@ export class SourceMappingVisitor extends RuleWalker { const consumer = new SourceMapConsumer(this.codeWithMap.map); start = this.getMappedPosition(start, consumer); end = this.getMappedPosition(end, consumer); + } else { + start += this.basePosition; + end = start + length; } return super.createFailure(start, end - start, message); } private getMappedPosition(pos: number, consumer: SourceMapConsumer) { - const absPos = findLineAndColumnNumber(pos - this.basePosition, this.codeWithMap.code); + const absPos = findLineAndColumnNumber(pos, this.codeWithMap.code); const mappedPos = consumer.originalPositionFor(absPos); - console.log(absPos, mappedPos); const char = findCharNumberFromLineAndColumn(mappedPos, this.codeWithMap.source); return char + this.basePosition; } diff --git a/src/angular/urlResolvers/absoluteResolver.ts b/src/angular/urlResolvers/absoluteResolver.ts deleted file mode 100644 index 74ee980a1..000000000 --- a/src/angular/urlResolvers/absoluteResolver.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as ts from 'typescript'; -import {AbstractResolver, MetadataUrls} from './abstractResolver'; -import {join} from 'path'; - -export class AbsoluteResolver extends AbstractResolver { - constructor(private basePath: string = '') { - super(); - } - - resolve(decorator: ts.Decorator) { - const arg = this.getDecoratorArgument(decorator); - if (!arg) { - return null; - } - let templateUrl = this.getTemplateUrl(decorator); - if (templateUrl) { - templateUrl = join(this.basePath, templateUrl); - } - return { - templateUrl: templateUrl, - styleUrls: this.getStyleUrls(decorator) - .map((p: string) => join(this.basePath, p)) - }; - } -} diff --git a/src/angular/urlResolvers/abstractResolver.ts b/src/angular/urlResolvers/abstractResolver.ts index b2641c1ea..8d31cda45 100644 --- a/src/angular/urlResolvers/abstractResolver.ts +++ b/src/angular/urlResolvers/abstractResolver.ts @@ -12,7 +12,7 @@ export interface MetadataUrls { export abstract class AbstractResolver { abstract resolve(decorator: ts.Decorator): MetadataUrls; - protected getTemplateUrl(decorator: ts.Decorator) { + protected getTemplateUrl(decorator: ts.Decorator): string { const arg = this.getDecoratorArgument(decorator); if (!arg) { return null; @@ -32,7 +32,7 @@ export abstract class AbstractResolver { } } - protected getStyleUrls(decorator: ts.Decorator) { + protected getStyleUrls(decorator: ts.Decorator): string[] { const arg = this.getDecoratorArgument(decorator); if (!arg) { return []; diff --git a/src/angular/urlResolvers/commonJsResolver.ts b/src/angular/urlResolvers/commonJsResolver.ts deleted file mode 100644 index ada65b8eb..000000000 --- a/src/angular/urlResolvers/commonJsResolver.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as ts from 'typescript'; -import {current} from '../../util/syntaxKind'; - -import {AbstractResolver, MetadataUrls} from './abstractResolver'; -import {dirname, join} from 'path'; - -const kinds = current(); - -const getProgramFilePath = (d: any) => { - let current: any = d; - while (current) { - if (current.kind === kinds.SourceFile) { - return current.path || current.fileName; - } - current = current.parent; - } - return undefined; -}; - -export class CommonJsResolver extends AbstractResolver { - - resolve(decorator: ts.Decorator) { - const arg = this.getDecoratorArgument(decorator); - if (!arg) { - return null; - } - const prop = arg.properties.filter((p: ts.PropertyAssignment) => { - if ((p.name).text === 'moduleId') { - return true; - } - return false; - }).pop(); - if (!prop) { - return null; - } - const i = (prop).initializer; - if (i && i.kind === kinds.PropertyAccessExpression && - i.expression && i.expression.text === 'module' && - i.name && i.name.text === 'id') { - const path = getProgramFilePath(decorator); - const dir = dirname(path); - return { - templateUrl: join(dir, this.getTemplateUrl(decorator)), - styleUrls: this.getStyleUrls(decorator) - .map((p: string) => join(dir, p)) - }; - } else { - return null; - } - } -} - diff --git a/src/angular/urlResolvers/pathResolver.ts b/src/angular/urlResolvers/pathResolver.ts new file mode 100644 index 000000000..59adcae8f --- /dev/null +++ b/src/angular/urlResolvers/pathResolver.ts @@ -0,0 +1,10 @@ +import {join} from 'path'; + +export class PathResolver { + resolve(path: string, relative: string): string { + if (typeof path !== 'string') { + return null; + } + return join(relative, path); + } +} diff --git a/src/angular/urlResolvers/urlResolver.ts b/src/angular/urlResolvers/urlResolver.ts index e635d9767..765092200 100644 --- a/src/angular/urlResolvers/urlResolver.ts +++ b/src/angular/urlResolvers/urlResolver.ts @@ -2,31 +2,46 @@ import * as ts from 'typescript'; import {Config} from '../config'; import {AbstractResolver, MetadataUrls} from './abstractResolver'; +import {dirname} from 'path'; +import {current} from '../../util/syntaxKind'; +import {PathResolver} from './pathResolver'; -import {CommonJsResolver} from './commonJsResolver'; -import {AbsoluteResolver} from './absoluteResolver'; +const kinds = current(); export class UrlResolver extends AbstractResolver { - private resolvers = [ - new CommonJsResolver(), - new AbsoluteResolver(Config.basePath) - ]; + constructor(private pathResolver: PathResolver) { + super(); + } - resolve(d: ts.Decorator) { - let result: MetadataUrls = null; - for (let i = 0; i < this.resolvers.length; i += 1) { - const resolver = this.resolvers[i]; - result = resolver.resolve(d); - if (result) { - break; + resolve(d: ts.Decorator): MetadataUrls { + const templateUrl = this.getTemplateUrl(d); + const styleUrls = this.getStyleUrls(d); + const targetPath = this.getProgramFilePath(d); + if (targetPath) { + const componentPath = dirname(targetPath); + return { + templateUrl: Config.resolveUrl(this.pathResolver.resolve(templateUrl, componentPath), d), + styleUrls: styleUrls.map((p: string) => { + return Config.resolveUrl(this.pathResolver.resolve(p, componentPath), d); + }) + }; + } else { + return { + templateUrl: Config.resolveUrl(null, d), + styleUrls: [] + }; + } + } + + private getProgramFilePath(d: any) { + let current: any = d; + while (current) { + if (current.kind === kinds.SourceFile) { + return current.path || current.fileName; } + current = current.parent; } - result.templateUrl = Config.resolveUrl(result.templateUrl, d); - result.styleUrls = result.styleUrls.map((s: string) => { - return Config.resolveUrl(s, d); - }); - return result; + return undefined; } } - diff --git a/src/angular/urlResolvers/webpackResolver.ts b/src/angular/urlResolvers/webpackResolver.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/noAccessMissingMemberRule.ts b/src/noAccessMissingMemberRule.ts index b285b2212..d4ace07e3 100644 --- a/src/noAccessMissingMemberRule.ts +++ b/src/noAccessMissingMemberRule.ts @@ -66,7 +66,7 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor { failureString += ` Probably you mean: ${getSuggestion(top.map(s => s.element))}.`; } const width = ast.name.length; - this.addFailure(this.createFailure(this.basePosition + ast.span.start, width, failureString)); + this.addFailure(this.createFailure(ast.span.start, width, failureString)); } return null; } diff --git a/src/noUnusedCssRule.ts b/src/noUnusedCssRule.ts index a8b74c2be..c53924b46 100644 --- a/src/noUnusedCssRule.ts +++ b/src/noUnusedCssRule.ts @@ -148,7 +148,7 @@ class UnusedCssVisitor extends BasicCssAstVisitor { try { const match = ast.selectors.some(s => this.visitCssSelector(s)); if (!match) { - this.addFailure(this.createFailure(this.templateStart + ast.start.offset, + this.addFailure(this.createFailure(ast.start.offset, ast.end.offset - ast.start.offset, 'Unused styles')); } } catch (e) { diff --git a/src/templatesUsePublicRule.ts b/src/templatesUsePublicRule.ts index 2feecc070..ef691f337 100644 --- a/src/templatesUsePublicRule.ts +++ b/src/templatesUsePublicRule.ts @@ -44,7 +44,7 @@ class SymbolAccessValidator extends RecursiveAngularExpressionVisitor { const width = ast.name.length; if (!isPublic) { const failureString = 'You can bind only to public class members.'; - this.addFailure(this.createFailure(this.basePosition + ast.span.start, width, failureString)); + this.addFailure(this.createFailure(ast.span.start, width, failureString)); } } } diff --git a/test/angular/metadataReader.spec.ts b/test/angular/metadataReader.spec.ts index a80db043d..42116cbfe 100644 --- a/test/angular/metadataReader.spec.ts +++ b/test/angular/metadataReader.spec.ts @@ -124,11 +124,10 @@ describe('metadataReader', () => { }); - it('should work with relative paths when module.id is set', () => { + it('should work with relative paths', () => { const code = ` @Component({ selector: 'foo', - moduleId: module.id, templateUrl: 'foo.html', styles: [\`baz\`] }) @@ -147,12 +146,11 @@ describe('metadataReader', () => { chai.expect(m.styles[0].url).eq(null); }); - it('should work with absolute paths when module.id is not set', () => { - Config.basePath = __dirname; + it('should work with absolute paths', () => { const code = ` @Component({ selector: 'foo', - templateUrl: '../../test/fixtures/metadataReader/moduleid/foo.html', + templateUrl: '/foo.html', styles: [\`baz\`] }) class Bar {} @@ -172,6 +170,7 @@ describe('metadataReader', () => { it('should work invoke Config.resolveUrl after all resolves', () => { let invoked = false; + const bak = Config.resolveUrl; Config.resolveUrl = (url: string) => { invoked = true; chai.expect(url.startsWith(normalize(join(__dirname, '../..')))).eq(true); @@ -199,7 +198,92 @@ describe('metadataReader', () => { chai.expect(m.styles[0].style.code).eq('baz'); chai.expect(m.styles[0].url).eq(null); chai.expect(invoked).eq(true); - Config.resolveUrl = (url: string) => url; + Config.resolveUrl = bak; + }); + + it('should work invoke Config.transformTemplate', () => { + let invoked = false; + const bak = Config.transformTemplate; + Config.transformTemplate = (code: string) => { + invoked = true; + chai.expect(code).eq('
\n'); + return { code }; + }; + const code = ` + @Component({ + selector: 'foo', + moduleId: module.id, + templateUrl: 'foo.html', + styles: [\`baz\`] + }) + class Bar {} + `; + const reader = new MetadataReader(new FsFileResolver()); + const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); + const classDeclaration = ast.statements.pop(); + chai.expect(invoked).eq(false); + const metadata = reader.read(classDeclaration); + chai.expect(metadata instanceof ComponentMetadata).eq(true); + chai.expect(metadata.selector).eq('foo'); + const m = metadata; + chai.expect(m.template.template.code).eq('
\n'); + chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.styles[0].style.code).eq('baz'); + chai.expect(m.styles[0].url).eq(null); + chai.expect(invoked).eq(true); + Config.transformTemplate = bak; + }); + + it('should work invoke Config.transformStyle', () => { + let invoked = false; + const bak = Config.transformStyle; + Config.transformStyle = (code: string) => { + invoked = true; + chai.expect(code).eq('baz'); + return { code }; + }; + const code = ` + @Component({ + selector: 'foo', + moduleId: module.id, + templateUrl: 'foo.html', + styles: [\`baz\`] + }) + class Bar {} + `; + const reader = new MetadataReader(new FsFileResolver()); + const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/moduleid/foo.ts'); + const classDeclaration = ast.statements.pop(); + chai.expect(invoked).eq(false); + const metadata = reader.read(classDeclaration); + chai.expect(metadata instanceof ComponentMetadata).eq(true); + chai.expect(metadata.selector).eq('foo'); + const m = metadata; + chai.expect(m.template.template.code).eq('
\n'); + chai.expect(m.template.url.endsWith('foo.html')).eq(true); + chai.expect(m.styles[0].style.code).eq('baz'); + chai.expect(m.styles[0].url).eq(null); + chai.expect(invoked).eq(true); + Config.transformStyle = bak; + }); + + it('should compile sass by default', () => { + const code = ` + @Component({ + selector: 'foo', + styleUrls: ['bar.scss'] + }) + class Bar {} + `; + const reader = new MetadataReader(new FsFileResolver()); + const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/sass/bar.ts'); + const classDeclaration = ast.statements.pop(); + const metadata = reader.read(classDeclaration); + chai.expect(metadata instanceof ComponentMetadata).eq(true); + chai.expect(metadata.selector).eq('foo'); + const m = metadata; + chai.expect(m.styles[0].url.endsWith('/metadataReader/sass/bar.scss')).eq(true); + chai.expect(m.styles[0].style.map === null).eq(false); }); }); diff --git a/test/angular/sourceMappingVisitor.spec.ts b/test/angular/sourceMappingVisitor.spec.ts new file mode 100644 index 000000000..375636219 --- /dev/null +++ b/test/angular/sourceMappingVisitor.spec.ts @@ -0,0 +1,48 @@ +import * as ts from 'typescript'; +import chai = require('chai'); + +import {getDecoratorPropertyInitializer} from '../../src/util/utils'; +import {SourceMappingVisitor} from '../../src/angular/sourceMappingVisitor'; +import {join, normalize} from 'path'; +import {renderSync} from 'node-sass'; + +const getAst = (code: string, file = 'file.ts') => { + return ts.createSourceFile(file, code, ts.ScriptTarget.ES2015, true); +}; + +const fixture1 = +`@Component({ + styles: [ + \` + .foo { + .bar { + color: red; + } + } + \` + ] +}) +export class Foo {} +`; + +describe('metadataReader', () => { + + it('should map to correct position', () => { + const ast = getAst(fixture1); + const classDeclaration = ast.statements.pop(); + const styles = getDecoratorPropertyInitializer(classDeclaration.decorators.pop(), 'styles'); + const styleNode = styles.elements[0]; + const scss = (styleNode).text; + const result = renderSync({ outFile: '/tmp/bar', data: scss, sourceMap: true }); + const visitor = new SourceMappingVisitor(ast, { disabledIntervals: null, ruleName: 'foo', ruleArguments: [] }, { + code: result.css.toString(), + map: JSON.parse(result.map.toString()), + source: scss + }, styleNode.getStart() + 1); + const failure = visitor.createFailure(0, 4, 'bar'); + chai.expect(failure.getStartPosition().getPosition()).eq(46); + chai.expect(failure.getEndPosition().getPosition()).eq(50); + chai.expect(fixture1.substring(failure.getStartPosition().getPosition(), failure.getEndPosition().getPosition())).eq('.foo'); + console.log(styles.elements[0].text, result.css.toString()); + }); +}); diff --git a/test/fixtures/metadataReader/sass/bar.scss b/test/fixtures/metadataReader/sass/bar.scss new file mode 100644 index 000000000..522dbc9f9 --- /dev/null +++ b/test/fixtures/metadataReader/sass/bar.scss @@ -0,0 +1,20 @@ +@function getColumnWidth($width, $columns,$margin){ + @return ($width / $columns) - ($margin * 2); +} + +$container-width: 100%; +$column-count: 4; +$margin: 1%; + +.container { + width: $container-width; +} + +.column { + background: #1abc9c; + height: 200px; + display: block; + float: left; + width: getColumnWidth($container-width,$column-count,$margin); + margin: 0 $margin; +} diff --git a/test/noUnusedCssRule.spec.ts b/test/noUnusedCssRule.spec.ts index 5776f83ed..7b8db350d 100644 --- a/test/noUnusedCssRule.spec.ts +++ b/test/noUnusedCssRule.spec.ts @@ -6,7 +6,7 @@ import {assertFailure, assertSuccess} from './testHelper'; import {Config} from '../src/angular/config'; describe('no-unused-css', () => { - xdescribe('valid cases', () => { + describe('valid cases', () => { it('should succeed when having valid simple selector', () => { let source = ` @@ -204,7 +204,7 @@ describe('no-unused-css', () => { }); - xdescribe('failures', () => { + describe('failures', () => { it('should fail when having a complex selector that doesn\'t match anything', () => { let source = ` @Component({ @@ -346,7 +346,7 @@ describe('no-unused-css', () => { }); }); - xdescribe('host', () => { + describe('host', () => { it('should never fail for :host', () => { let source = ` @@ -405,7 +405,7 @@ describe('no-unused-css', () => { }); }); - xdescribe('deep and >>>', () => { + describe('deep and >>>', () => { it('should ignore deep and match only before it', () => { let source = ` @Component({ @@ -519,7 +519,7 @@ describe('no-unused-css', () => { }); }); - xdescribe('pseudo', () => { + describe('pseudo', () => { it('should ignore before and after', () => { let source = ` @@ -578,7 +578,7 @@ describe('no-unused-css', () => { }); }); - xdescribe('ViewEncapsulation', () => { + describe('ViewEncapsulation', () => { it('should ignore before and after', () => { let source = ` @Component({ @@ -748,7 +748,7 @@ describe('no-unused-css', () => { Config.transformStyle = (code: string) => ({ code, map: null }); }); - xdescribe('inconsistencies with template', () => { + describe('inconsistencies with template', () => { it('should ignore misspelled template', () => { let source = ` From e9575fbe30f932c29fce6a870ba2d8e194700a87 Mon Sep 17 00:00:00 2001 From: mgechev Date: Fri, 25 Nov 2016 00:26:27 -0800 Subject: [PATCH 05/14] fix(css): set proper base position Fix #166 --- src/angular/ng2Walker.ts | 14 ++++++- test/angular/sourceMappingVisitor.spec.ts | 1 - test/noUnusedCssRule.spec.ts | 48 +++++++++++------------ 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts index f125e4d3b..b510bb092 100644 --- a/src/angular/ng2Walker.ts +++ b/src/angular/ng2Walker.ts @@ -119,10 +119,20 @@ export class Ng2Walker extends Lint.RuleWalker { protected visitNg2Component(metadata: ComponentMetadata) { const template = metadata.template; + const getPosition = (node: any) => { + let pos = 0; + if (node) { + pos = node.pos + 1; + try { + pos = node.getStart() + 1; + } catch (e) {} + } + return pos; + }; if (template && template.template) { try { const templateAst = parseTemplate(template.template.code, Config.predefinedDirectives); - this.visitNg2TemplateHelper(templateAst, metadata, template.node ? template.node.pos + 2 : 0); + this.visitNg2TemplateHelper(templateAst, metadata, getPosition(template.node)); } catch (e) { console.log(e); console.log('Cannot parse the template of', ((metadata.controller || {}).name || {}).text); @@ -133,7 +143,7 @@ export class Ng2Walker extends Lint.RuleWalker { for (let i = 0; i < styles.length; i += 1) { const style = styles[i]; try { - this.visitNg2StyleHelper(parseCss(style.style.code), metadata, style, style.node ? style.node.pos + 2 : 0); + this.visitNg2StyleHelper(parseCss(style.style.code), metadata, style, getPosition(style.node)); } catch (e) { console.log('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text); } diff --git a/test/angular/sourceMappingVisitor.spec.ts b/test/angular/sourceMappingVisitor.spec.ts index 375636219..0be3e4fcd 100644 --- a/test/angular/sourceMappingVisitor.spec.ts +++ b/test/angular/sourceMappingVisitor.spec.ts @@ -42,7 +42,6 @@ describe('metadataReader', () => { const failure = visitor.createFailure(0, 4, 'bar'); chai.expect(failure.getStartPosition().getPosition()).eq(46); chai.expect(failure.getEndPosition().getPosition()).eq(50); - chai.expect(fixture1.substring(failure.getStartPosition().getPosition(), failure.getEndPosition().getPosition())).eq('.foo'); console.log(styles.elements[0].text, result.css.toString()); }); }); diff --git a/test/noUnusedCssRule.spec.ts b/test/noUnusedCssRule.spec.ts index 7b8db350d..4ec14b8d1 100644 --- a/test/noUnusedCssRule.spec.ts +++ b/test/noUnusedCssRule.spec.ts @@ -229,11 +229,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 12 }, endPosition: { line: 12, - character: 0 + character: 12 } }); }); @@ -267,11 +267,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 15, - character: 0 + character: 12 }, endPosition: { line: 17, - character: 0 + character: 12 } }); }); @@ -300,11 +300,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 12 }, endPosition: { line: 12, - character: 0 + character: 12 } }); }); @@ -335,11 +335,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 14 }, endPosition: { line: 12, - character: 0 + character: 14 } }); }); @@ -395,11 +395,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 12 }, endPosition: { line: 12, - character: 0 + character: 12 } }); }); @@ -453,11 +453,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 12 }, endPosition: { line: 12, - character: 0 + character: 12 } }); }); @@ -509,11 +509,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 12 }, endPosition: { line: 12, - character: 0 + character: 12 } }); }); @@ -568,11 +568,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 10, - character: 0 + character: 12 }, endPosition: { line: 12, - character: 0 + character: 12 } }); }); @@ -634,11 +634,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 7, - character: 0 + character: 12 }, endPosition: { line: 9, - character: 0 + character: 12 } }); }); @@ -662,11 +662,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 7, - character: 0 + character: 12 }, endPosition: { line: 9, - character: 0 + character: 12 } }); }); @@ -690,11 +690,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 7, - character: 0 + character: 12 }, endPosition: { line: 9, - character: 0 + character: 12 } }); }); @@ -738,11 +738,11 @@ describe('no-unused-css', () => { message: 'Unused styles', startPosition: { line: 9, - character: 0 + character: 8 }, endPosition: { line: 13, - character: 3 + character: 11 } }); Config.transformStyle = (code: string) => ({ code, map: null }); From 1d0db9f5d539bed9f713f8c7fa3ba1f0eed8cc52 Mon Sep 17 00:00:00 2001 From: mgechev Date: Fri, 25 Nov 2016 17:48:13 -0800 Subject: [PATCH 06/14] feat: add logger --- package.json | 3 -- src/angular/config.ts | 24 ++++++------ src/angular/metadataReader.ts | 74 ++++++++++++++++++++--------------- src/angular/ng2Walker.ts | 6 +-- src/noUnusedCssRule.ts | 5 ++- src/util/logger.ts | 25 ++++++++++++ 6 files changed, 86 insertions(+), 51 deletions(-) create mode 100644 src/util/logger.ts diff --git a/package.json b/package.json index 9c51066c1..3cfd09d84 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "@types/less": "0.0.31", "@types/mocha": "^2.2.32", "@types/node": "^6.0.41", - "@types/node-sass": "^3.10.31", "@types/source-map": "^0.5.0", "@types/sprintf-js": "0.0.27", "chai": "^3.5.0", @@ -72,8 +71,6 @@ "app-root-path": "^2.0.1", "css-selector-tokenizer": "^0.7.0", "cssauron": "^1.4.0", - "less": "^2.7.1", - "node-sass": "^3.13.0", "source-map": "^0.5.6", "sprintf-js": "^1.0.3" } diff --git a/src/angular/config.ts b/src/angular/config.ts index 83356b29c..12c8b1c72 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -17,12 +17,20 @@ export interface StyleTransformer { (style: string, url: string, d: ts.Decorator): CodeWithSourceMap; } +export const LogLevel = { + Error: 0b001, + Info: 0b011, + Debug: 0b111, + None: 0 +}; + export interface Config { interpolation: [string, string]; resolveUrl: UrlResolver; transformTemplate: TemplateTransformer; transformStyle: StyleTransformer; predefinedDirectives: DirectiveDeclaration[]; + logLevel: number; } export interface DirectiveDeclaration { @@ -38,24 +46,18 @@ export let Config: Config = { }, transformTemplate(code: string, url: string, d: ts.Decorator) { - return { code }; + return { code, url }; }, transformStyle(code: string, url: string, d: ts.Decorator) { - let result: CodeWithSourceMap = { source: code, code }; - if (url && (url.endsWith('.sass') || url.endsWith('.scss'))) { - // Note: it's not written on disk - // https://github.com/sass/node-sass#outfile - let res = renderSync({ file: url, data: code, sourceMap: true, outFile: url }); - result.map = JSON.parse(res.map.toString()); - result.code = res.css.toString(); - } - return { code }; + return { code, url }; }, predefinedDirectives: [ { selector: 'form', exportAs: 'ngForm' } - ] + ], + + logLevel: LogLevel.None }; try { diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index 56b4f8d40..0e3437c7b 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -6,9 +6,10 @@ const kinds = current(); import {Config} from './config'; import {FileResolver} from './fileResolver/fileResolver'; -import {AbstractResolver} from './urlResolvers/abstractResolver'; +import {AbstractResolver, MetadataUrls} from './urlResolvers/abstractResolver'; import {UrlResolver} from './urlResolvers/urlResolver'; import {PathResolver} from './urlResolvers/pathResolver'; +import {logger} from '../util/logger'; import {DirectiveMetadata, ComponentMetadata, StylesMetadata, CodeWithSourceMap} from './metadata'; @@ -74,55 +75,49 @@ export class MetadataReader { return metadata; } - readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator) { - const expr = this.getDecoratorArgument(dec); - const metadata = this.readDirectiveMetadata(d, dec); - const result = new ComponentMetadata(); - if (!expr) { - return result; - } - result.selector = metadata.selector; - result.controller = metadata.controller; + readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls) { const inlineTemplate = getDecoratorPropertyInitializer(dec, 'template'); - const external = this._urlResolver.resolve(dec); if (inlineTemplate && isSimpleTemplateString(inlineTemplate)) { const transformed = normalizeTransformed(Config.transformTemplate(inlineTemplate.text, null, dec)); - result.template = { + return { template: transformed, url: null, node: inlineTemplate }; + } else { + if (external.templateUrl) { + try { + const template = this._fileResolver.resolve(external.templateUrl); + const transformed = normalizeTransformed(Config.transformTemplate(template, external.templateUrl, dec)); + return { + template: transformed, + url: external.templateUrl, + node: null + }; + } catch (e) { + logger.info('Cannot read the external template ' + external.templateUrl); + } + } } + } + + readComponentStylesMetadata(dec: ts.Decorator, external: MetadataUrls) { const inlineStyles = getDecoratorPropertyInitializer(dec, 'styles'); + let styles: any[]; if (inlineStyles && inlineStyles.kind === kinds.ArrayLiteralExpression) { inlineStyles.elements.forEach((inlineStyle: any) => { if (isSimpleTemplateString(inlineStyle)) { - result.styles = result.styles || []; - result.styles.push({ + styles = styles || []; + styles.push({ style: normalizeTransformed(Config.transformStyle(inlineStyle.text, null, dec)), url: null, node: inlineStyle, }); } }); - } - if (!result.template && external.templateUrl) { - try { - const template = this._fileResolver.resolve(external.templateUrl); - const transformed = normalizeTransformed(Config.transformTemplate(template, external.templateUrl, dec)); - result.template = { - template: transformed, - url: external.templateUrl, - node: null - }; - } catch (e) { - console.log(e); - console.log('Cannot read the external template ' + external.templateUrl); - } - } - if (!result.styles || !result.styles.length) { + } else if (external.styleUrls) { try { - result.styles = external.styleUrls.map((url: string) => { + styles = external.styleUrls.map((url: string) => { const style = this._fileResolver.resolve(url); const transformed = normalizeTransformed(Config.transformStyle(style, url, dec)); return { @@ -131,9 +126,24 @@ export class MetadataReader { }; }); } catch (e) { - console.log('Unable to read external style. ' + e.toString()); + logger.info('Unable to read external style. ' + e.toString()); } } + return styles; + } + + readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator) { + const expr = this.getDecoratorArgument(dec); + const metadata = this.readDirectiveMetadata(d, dec); + const result = new ComponentMetadata(); + result.selector = metadata.selector; + result.controller = metadata.controller; + if (!expr) { + return result; + } + const external = this._urlResolver.resolve(dec); + result.template = this.readComponentTemplateMetadata(dec, external); + result.styles = this.readComponentStylesMetadata(dec, external); return result; } diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts index b510bb092..cedd544ed 100644 --- a/src/angular/ng2Walker.ts +++ b/src/angular/ng2Walker.ts @@ -20,6 +20,7 @@ import {ng2WalkerFactoryUtils} from './ng2WalkerFactoryUtils'; import {ComponentMetadata, DirectiveMetadata, StyleMetadata} from './metadata'; import {Config} from './config'; +import {logger} from '../util/logger'; import SyntaxKind = require('../util/syntaxKind'); @@ -134,8 +135,7 @@ export class Ng2Walker extends Lint.RuleWalker { const templateAst = parseTemplate(template.template.code, Config.predefinedDirectives); this.visitNg2TemplateHelper(templateAst, metadata, getPosition(template.node)); } catch (e) { - console.log(e); - console.log('Cannot parse the template of', ((metadata.controller || {}).name || {}).text); + logger.error('Cannot parse the template of', ((metadata.controller || {}).name || {}).text); } } const styles = metadata.styles; @@ -145,7 +145,7 @@ export class Ng2Walker extends Lint.RuleWalker { try { this.visitNg2StyleHelper(parseCss(style.style.code), metadata, style, getPosition(style.node)); } catch (e) { - console.log('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text); + logger.error('Cannot parse the styles of', ((metadata.controller || {}).name || {}).text); } } } diff --git a/src/noUnusedCssRule.ts b/src/noUnusedCssRule.ts index c53924b46..f2da5827f 100644 --- a/src/noUnusedCssRule.ts +++ b/src/noUnusedCssRule.ts @@ -14,6 +14,7 @@ import {CssAst, CssSelectorRuleAst, CssSelectorAst} from './angular/styles/cssAs import {ComponentMetadata, StyleMetadata} from './angular/metadata'; import {ng2WalkerFactoryUtils} from './angular/ng2WalkerFactoryUtils'; +import {logger} from './util/logger'; const CssSelectorTokenizer = require('css-selector-tokenizer'); @@ -152,7 +153,7 @@ class UnusedCssVisitor extends BasicCssAstVisitor { ast.end.offset - ast.start.offset, 'Unused styles')); } } catch (e) { - console.error(e); + logger.error(e); } return true; } @@ -212,7 +213,7 @@ export class UnusedCssNg2Visitor extends Ng2Walker { this.templateAst = new ElementAst('*', [], [], [], [], [], [], false, parseTemplate(meta.template.template.code), 0, null, null); } catch (e) { - console.error('Cannot parse the template', e); + logger.error('Cannot parse the template', e); } } } diff --git a/src/util/logger.ts b/src/util/logger.ts new file mode 100644 index 000000000..22d455554 --- /dev/null +++ b/src/util/logger.ts @@ -0,0 +1,25 @@ +import {Config, LogLevel} from '../angular/config'; + +class Logger { + constructor(private level: number) {} + + error(...msg: string[]) { + if (this.level & LogLevel.Error) { + console.error.apply(console, msg); + } + } + + info(...msg: string[]) { + if (this.level && LogLevel.Info) { + console.error.apply(console, msg); + } + } + + debug(...msg: string[]) { + if (this.level && LogLevel.Debug) { + console.error.apply(console, msg); + } + } +} + +export const logger = new Logger(Config.logLevel); From 8dc2065cf3571742366b4c497a97114818385590 Mon Sep 17 00:00:00 2001 From: mgechev Date: Fri, 25 Nov 2016 21:44:05 -0800 Subject: [PATCH 07/14] refactor: configuration --- src/angular/config.ts | 17 +++++++---------- src/angular/fileResolver/fileResolver.ts | 1 - src/angular/metadataReader.ts | 10 ++++++---- src/angular/ng2Walker.ts | 11 ++++------- src/angular/styles/basicCssAstVisitor.ts | 3 ++- .../recursiveAngularExpressionVisitor.ts | 1 + src/angular/templates/templateParser.ts | 3 ++- src/angular/urlResolvers/pathResolver.ts | 2 +- src/index.ts | 1 + src/util/logger.ts | 4 ++-- 10 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/angular/config.ts b/src/angular/config.ts index 12c8b1c72..e7b54adc8 100644 --- a/src/angular/config.ts +++ b/src/angular/config.ts @@ -1,9 +1,5 @@ import * as ts from 'typescript'; import {CodeWithSourceMap} from './metadata'; -import {renderSync} from 'node-sass'; - -const root = require('app-root-path'); -const join = require('path').join; export interface UrlResolver { (url: string, d: ts.Decorator): string; @@ -18,10 +14,10 @@ export interface StyleTransformer { } export const LogLevel = { + None: 0, Error: 0b001, Info: 0b011, - Debug: 0b111, - None: 0 + Debug: 0b111 }; export interface Config { @@ -38,7 +34,7 @@ export interface DirectiveDeclaration { exportAs: string; } -export let Config: Config = { +export const Config: Config = { interpolation: ['{{', '}}'], resolveUrl(url: string, d: ts.Decorator) { @@ -60,10 +56,11 @@ export let Config: Config = { logLevel: LogLevel.None }; +const root = require('app-root-path'); + try { - let newConfig = require(join(root.path, '.codelyzer')); + let newConfig = require(root.path + '/.codelyzer'); Object.assign(Config, newConfig); } catch (e) { - + console.info('Cannot find ".codelyzer.js" in the root of the project'); } - diff --git a/src/angular/fileResolver/fileResolver.ts b/src/angular/fileResolver/fileResolver.ts index 0cf0bf64b..2a4aee9c7 100644 --- a/src/angular/fileResolver/fileResolver.ts +++ b/src/angular/fileResolver/fileResolver.ts @@ -1,4 +1,3 @@ export abstract class FileResolver { abstract resolve(path: string): string; } - diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index 0e3437c7b..3dbcba9f7 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -1,18 +1,20 @@ import * as ts from 'typescript'; import {current} from '../util/syntaxKind'; -import {isSimpleTemplateString, getDecoratorPropertyInitializer} from '../util/utils'; - -const kinds = current(); -import {Config} from './config'; import {FileResolver} from './fileResolver/fileResolver'; import {AbstractResolver, MetadataUrls} from './urlResolvers/abstractResolver'; import {UrlResolver} from './urlResolvers/urlResolver'; import {PathResolver} from './urlResolvers/pathResolver'; + import {logger} from '../util/logger'; +import {isSimpleTemplateString, getDecoratorPropertyInitializer} from '../util/utils'; + +import {Config} from './config'; import {DirectiveMetadata, ComponentMetadata, StylesMetadata, CodeWithSourceMap} from './metadata'; +const kinds = current(); + const normalizeTransformed = (t: CodeWithSourceMap) => { if (!t.map) { t.source = t.code; diff --git a/src/angular/ng2Walker.ts b/src/angular/ng2Walker.ts index cedd544ed..fec90f160 100644 --- a/src/angular/ng2Walker.ts +++ b/src/angular/ng2Walker.ts @@ -1,9 +1,6 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import * as compiler from '@angular/compiler'; -import { - TemplateAst -} from '@angular/compiler'; import { parseTemplate } from './templates/templateParser'; import {parseCss} from './styles/parseCss'; @@ -11,18 +8,18 @@ import {CssAst} from './styles/cssAst'; import {BasicCssAstVisitor, CssAstVisitorCtrl} from './styles/basicCssAstVisitor'; import {RecursiveAngularExpressionVisitorCtr, BasicTemplateAstVisitor, TemplateAstVisitorCtr} from './templates/basicTemplateAstVisitor'; -import { RecursiveAngularExpressionVisitor } from './templates/recursiveAngularExpressionVisitor'; +import {RecursiveAngularExpressionVisitor} from './templates/recursiveAngularExpressionVisitor'; -import {getDecoratorName, isSimpleTemplateString, getDecoratorPropertyInitializer} from '../util/utils'; import {MetadataReader} from './metadataReader'; +import {ComponentMetadata, DirectiveMetadata, StyleMetadata} from './metadata'; import {ng2WalkerFactoryUtils} from './ng2WalkerFactoryUtils'; -import {ComponentMetadata, DirectiveMetadata, StyleMetadata} from './metadata'; import {Config} from './config'; import {logger} from '../util/logger'; -import SyntaxKind = require('../util/syntaxKind'); +import {getDecoratorName, isSimpleTemplateString, getDecoratorPropertyInitializer} from '../util/utils'; +import SyntaxKind = require('../util/syntaxKind'); const getDecoratorStringArgs = (decorator: ts.Decorator) => { let baseExpr = decorator.expression || {}; diff --git a/src/angular/styles/basicCssAstVisitor.ts b/src/angular/styles/basicCssAstVisitor.ts index efdde6edc..bfeca077b 100644 --- a/src/angular/styles/basicCssAstVisitor.ts +++ b/src/angular/styles/basicCssAstVisitor.ts @@ -1,6 +1,7 @@ -import * as ast from './cssAst'; import * as ts from 'typescript'; import * as Lint from 'tslint'; + +import * as ast from './cssAst'; import {SourceMappingVisitor} from '../sourceMappingVisitor'; import {ComponentMetadata, StyleMetadata} from '../metadata'; diff --git a/src/angular/templates/recursiveAngularExpressionVisitor.ts b/src/angular/templates/recursiveAngularExpressionVisitor.ts index 53cc20b81..702eea9de 100644 --- a/src/angular/templates/recursiveAngularExpressionVisitor.ts +++ b/src/angular/templates/recursiveAngularExpressionVisitor.ts @@ -1,6 +1,7 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; import * as e from '@angular/compiler/src/expression_parser/ast'; + import {SourceMappingVisitor} from '../sourceMappingVisitor'; import {ComponentMetadata} from '../metadata'; diff --git a/src/angular/templates/templateParser.ts b/src/angular/templates/templateParser.ts index 54ad690c4..0e2f3c8a3 100644 --- a/src/angular/templates/templateParser.ts +++ b/src/angular/templates/templateParser.ts @@ -1,7 +1,8 @@ import { __core_private__ as r, NO_ERRORS_SCHEMA } from '@angular/core'; -import { Config } from '../config'; import * as compiler from '@angular/compiler'; +import { Config } from '../config'; + let refId = 0; const dummyMetadataFactory = (selector: string, exportAs: string) => { diff --git a/src/angular/urlResolvers/pathResolver.ts b/src/angular/urlResolvers/pathResolver.ts index 59adcae8f..3ea55f358 100644 --- a/src/angular/urlResolvers/pathResolver.ts +++ b/src/angular/urlResolvers/pathResolver.ts @@ -6,5 +6,5 @@ export class PathResolver { return null; } return join(relative, path); - } + } } diff --git a/src/index.ts b/src/index.ts index 905c70f2c..b4db7e62e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,3 +18,4 @@ export { Rule as UseInputPropertyDecoratorRule } from './useInputPropertyDecorat export { Rule as UseLifeCycleInterfaceRule } from './useLifeCycleInterfaceRule'; export { Rule as UseOutputPropertyDecoratorRule } from './useOutputPropertyDecoratorRule'; export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule'; +export * from './angular/config'; diff --git a/src/util/logger.ts b/src/util/logger.ts index 22d455554..d10e2c278 100644 --- a/src/util/logger.ts +++ b/src/util/logger.ts @@ -11,13 +11,13 @@ class Logger { info(...msg: string[]) { if (this.level && LogLevel.Info) { - console.error.apply(console, msg); + console.info.apply(console, msg); } } debug(...msg: string[]) { if (this.level && LogLevel.Debug) { - console.error.apply(console, msg); + console.log.apply(console, msg); } } } From d8aa27880429468a591671c00da79fef92ccd500 Mon Sep 17 00:00:00 2001 From: mgechev Date: Thu, 24 Nov 2016 11:40:12 -0800 Subject: [PATCH 08/14] fix: drop missing ts type --- src/angular/metadataReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index 3dbcba9f7..dfa3d0688 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -65,7 +65,7 @@ export class MetadataReader { if (!expr) { return metadata; } - expr.properties.forEach((p: ts.ObjectLiteralElementLike) => { + expr.properties.forEach((p: any) => { if (p.kind !== kinds.PropertyAssignment) { return; } From bb0b5aa610f9e529eccbad725b3bd9f414a176dd Mon Sep 17 00:00:00 2001 From: mgechev Date: Sat, 26 Nov 2016 13:22:07 -0800 Subject: [PATCH 09/14] fix: add node-sass for tests --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 3cfd09d84..ce17a5e4c 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,14 @@ "@types/less": "0.0.31", "@types/mocha": "^2.2.32", "@types/node": "^6.0.41", + "@types/node-sass": "^3.10.31", "@types/source-map": "^0.5.0", "@types/sprintf-js": "0.0.27", "chai": "^3.5.0", "chai-spies": "^0.7.1", "minimalist": "1.0.0", "mocha": "3.0.2", + "node-sass": "^3.13.0", "rimraf": "^2.5.2", "rxjs": "5.0.0-beta.12", "ts-node": "1.2.2", From dfaafcb2a13589b2c350aa2ed659bafc447b52ae Mon Sep 17 00:00:00 2001 From: mgechev Date: Sat, 26 Nov 2016 16:16:36 -0800 Subject: [PATCH 10/14] feat(test): add spec for element ref --- test/angular/sourceMappingVisitor.spec.ts | 1 - test/noAccessMissingMemberRule.spec.ts | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test/angular/sourceMappingVisitor.spec.ts b/test/angular/sourceMappingVisitor.spec.ts index 0be3e4fcd..d480cf4bd 100644 --- a/test/angular/sourceMappingVisitor.spec.ts +++ b/test/angular/sourceMappingVisitor.spec.ts @@ -42,6 +42,5 @@ describe('metadataReader', () => { const failure = visitor.createFailure(0, 4, 'bar'); chai.expect(failure.getStartPosition().getPosition()).eq(46); chai.expect(failure.getEndPosition().getPosition()).eq(50); - console.log(styles.elements[0].text, result.css.toString()); }); }); diff --git a/test/noAccessMissingMemberRule.spec.ts b/test/noAccessMissingMemberRule.spec.ts index 70936b02c..c3a3850eb 100644 --- a/test/noAccessMissingMemberRule.spec.ts +++ b/test/noAccessMissingMemberRule.spec.ts @@ -334,6 +334,18 @@ describe('no-access-missing-member', () => { }); }); + it('should succeed with elementref', () => { + let source = ` + @Component({ + selector: 'foobar', + template: '{{ baz.value }}' + }) + class Test { + foo: number; + }`; + assertSuccess('no-access-missing-member', source); + }); + }); describe('valid expressions', () => { From 45db869fe27fdfcf881777b1812754d281b1b9f1 Mon Sep 17 00:00:00 2001 From: mgechev Date: Sun, 27 Nov 2016 11:10:27 -0800 Subject: [PATCH 11/14] chore: fix build in Windows --- test/angular/metadataReader.spec.ts | 32 ++++++----------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/test/angular/metadataReader.spec.ts b/test/angular/metadataReader.spec.ts index 42116cbfe..eb9eaaf88 100644 --- a/test/angular/metadataReader.spec.ts +++ b/test/angular/metadataReader.spec.ts @@ -140,7 +140,7 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('
\n'); + 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(null); @@ -162,7 +162,7 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('
\n'); + 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(null); @@ -193,7 +193,7 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('
\n'); + 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(null); @@ -206,7 +206,7 @@ describe('metadataReader', () => { const bak = Config.transformTemplate; Config.transformTemplate = (code: string) => { invoked = true; - chai.expect(code).eq('
\n'); + chai.expect(code.trim()).eq('
'); return { code }; }; const code = ` @@ -226,7 +226,7 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('
\n'); + 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(null); @@ -259,33 +259,13 @@ describe('metadataReader', () => { chai.expect(metadata instanceof ComponentMetadata).eq(true); chai.expect(metadata.selector).eq('foo'); const m = metadata; - chai.expect(m.template.template.code).eq('
\n'); + 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(null); chai.expect(invoked).eq(true); Config.transformStyle = bak; }); - - it('should compile sass by default', () => { - const code = ` - @Component({ - selector: 'foo', - styleUrls: ['bar.scss'] - }) - class Bar {} - `; - const reader = new MetadataReader(new FsFileResolver()); - const ast = getAst(code, __dirname + '/../../test/fixtures/metadataReader/sass/bar.ts'); - const classDeclaration = ast.statements.pop(); - const metadata = reader.read(classDeclaration); - chai.expect(metadata instanceof ComponentMetadata).eq(true); - chai.expect(metadata.selector).eq('foo'); - const m = metadata; - chai.expect(m.styles[0].url.endsWith('/metadataReader/sass/bar.scss')).eq(true); - chai.expect(m.styles[0].style.map === null).eq(false); - }); }); - }); From ee0894f7e155a133f26cda49131d6a71e3213096 Mon Sep 17 00:00:00 2001 From: mgechev Date: Sun, 27 Nov 2016 11:12:01 -0800 Subject: [PATCH 12/14] chore: update test scripts --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ce17a5e4c..66bb19156 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "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/** dist/test/** --recursive", - "test:watch": "rimraf dist && tsc && cp -r test/fixtures dist/test && mocha dist/test/** dist/test/** --watch --recursive", + "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 && mocha dist/test --watch --recursive", "tscv": "tsc --version", "tsc": "tsc", "tsc:watch": "tsc --w" From 18f14b2fe3deb836315f10545f159ae5c203fbee Mon Sep 17 00:00:00 2001 From: mgechev Date: Sun, 27 Nov 2016 11:15:28 -0800 Subject: [PATCH 13/14] ci: add appveyor --- appveyor.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..1cd44eea1 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,40 @@ +# AppVeyor file +# http://www.appveyor.com/docs/appveyor-yml +# This file: cloned from https://github.com/gruntjs/grunt/blob/master/appveyor.yml + +# Build version format +version: "{build}" + +# Test against this version of Node.js +environment: + nodejs_version: "Stable" + +build: off + +clone_depth: 10 + +# Fix line endings on Windows +init: + - git config --global core.autocrlf true + +install: + - ps: Install-Product node $env:nodejs_version + - npm install -g npm@3.10.8 + - ps: $env:path = $env:appdata + "\npm;" + $env:path + - npm install + +test_script: + # Output useful info for debugging. + - node --version && npm --version + # We test multiple Windows shells because of prior stdout buffering issues + # filed against Grunt. https://github.com/joyent/node/issues/3584 + - ps: "npm --version # PowerShell" # Pass comment to PS for easier debugging + - npm t + +notifications: + - provider: Webhook + url: https://webhooks.gitter.im/e/cfd8ce5ddee6f3a0b0c9 + on_build_success: false + on_build_failure: true + on_build_status_changed: true + From 9c86755e215e34599428e7ba69947d6ea3cbed32 Mon Sep 17 00:00:00 2001 From: mgechev Date: Sun, 27 Nov 2016 11:19:57 -0800 Subject: [PATCH 14/14] fix: typing error --- src/angular/metadataReader.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/angular/metadataReader.ts b/src/angular/metadataReader.ts index dfa3d0688..3c0a367b6 100644 --- a/src/angular/metadataReader.ts +++ b/src/angular/metadataReader.ts @@ -11,7 +11,7 @@ import {isSimpleTemplateString, getDecoratorPropertyInitializer} from '../util/u import {Config} from './config'; -import {DirectiveMetadata, ComponentMetadata, StylesMetadata, CodeWithSourceMap} from './metadata'; +import {DirectiveMetadata, ComponentMetadata, StylesMetadata, CodeWithSourceMap, TemplateMetadata} from './metadata'; const kinds = current(); @@ -77,7 +77,7 @@ export class MetadataReader { return metadata; } - readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls) { + readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls): TemplateMetadata { const inlineTemplate = getDecoratorPropertyInitializer(dec, 'template'); if (inlineTemplate && isSimpleTemplateString(inlineTemplate)) { const transformed = normalizeTransformed(Config.transformTemplate(inlineTemplate.text, null, dec));