-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of https://github.com/mgechev/codelyzer
* 'master' of https://github.com/mgechev/codelyzer: Adding NodeValidator to validate any node that matches a SyntaxKind A more functional approach to creating Walker Using WalkerFactory to create Ng2Walker First attempt at replacing AST traversal with Maybe<T>
- Loading branch information
Showing
9 changed files
with
561 additions
and
239 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,162 +1,155 @@ | ||
import * as ts from 'typescript'; | ||
import {current} from '../util/syntaxKind'; | ||
|
||
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, TemplateMetadata} from './metadata'; | ||
|
||
const kinds = current(); | ||
import {DirectiveMetadata, ComponentMetadata, CodeWithSourceMap, TemplateMetadata} from './metadata'; | ||
import {Maybe, unwrapFirst, ifTrue,} from '../util/function'; | ||
import { | ||
callExpression, withIdentifier, hasProperties, | ||
isSimpleTemplateString, getStringInitializerFromProperty, decoratorArgument | ||
} from '../util/astQuery'; | ||
import {getTemplate, getInlineStyle} from '../util/ngQuery'; | ||
|
||
const normalizeTransformed = (t: CodeWithSourceMap) => { | ||
if (!t.map) { | ||
t.source = t.code; | ||
} | ||
return t; | ||
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(new PathResolver()); | ||
} | ||
|
||
read(d: ts.ClassDeclaration): DirectiveMetadata { | ||
let directiveDecorator: ts.Decorator = null; | ||
let componentDecorator: ts.Decorator = null; | ||
(d.decorators || ([] as ts.Decorator[])).forEach((dec: ts.Decorator) => { | ||
let expr = dec.expression; | ||
if (expr && expr.kind === kinds.CallExpression && (<ts.CallExpression>expr).expression) { | ||
expr = (<ts.CallExpression>expr).expression; | ||
} | ||
const identifier = (<ts.Identifier>expr); | ||
if (expr && expr.kind === kinds.Identifier && identifier.text) { | ||
if (identifier.text === 'Component') { | ||
componentDecorator = dec; | ||
} else if (identifier.text === 'Directive') { | ||
directiveDecorator = dec; | ||
} | ||
} | ||
}); | ||
if (directiveDecorator) { | ||
return this.readDirectiveMetadata(d, directiveDecorator); | ||
constructor(private _fileResolver: FileResolver, private _urlResolver?: AbstractResolver) { | ||
this._urlResolver = this._urlResolver || new UrlResolver(new PathResolver()); | ||
} | ||
if (componentDecorator) { | ||
return this.readComponentMetadata(d, componentDecorator); | ||
|
||
read(d: ts.ClassDeclaration): DirectiveMetadata { | ||
let componentMetadata = unwrapFirst( | ||
(d.decorators || ([] as ts.Decorator[])).map((dec: ts.Decorator) => { | ||
return Maybe.lift(dec).bind(callExpression) | ||
.bind(withIdentifier('Component')) | ||
.fmap(() => this.readComponentMetadata(d, dec)); | ||
})); | ||
|
||
let directiveMetadata = unwrapFirst( | ||
(d.decorators || ([] as ts.Decorator[])).map((dec: ts.Decorator) => | ||
Maybe.lift(dec) | ||
.bind(callExpression) | ||
.bind(withIdentifier('Directive')) | ||
.fmap(() => this.readDirectiveMetadata(d, dec)) | ||
)); | ||
|
||
return directiveMetadata || componentMetadata || undefined; | ||
} | ||
return null; | ||
} | ||
|
||
readDirectiveMetadata(d: ts.ClassDeclaration, dec: ts.Decorator) { | ||
const expr = this.getDecoratorArgument(dec); | ||
const metadata = new DirectiveMetadata(); | ||
metadata.controller = d; | ||
metadata.decorator = dec; | ||
if (!expr) { | ||
return metadata; | ||
|
||
readDirectiveMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata { | ||
|
||
const selector = this.getDecoratorArgument(dec) | ||
.bind(expr => getStringInitializerFromProperty('selector', expr.properties)) | ||
.fmap(initializer => initializer.text); | ||
|
||
return Object.assign(new DirectiveMetadata(), { | ||
controller: d, | ||
decorator: dec, | ||
selector: selector.unwrap(), | ||
}); | ||
} | ||
expr.properties.forEach((p: any) => { | ||
if (p.kind !== kinds.PropertyAssignment) { | ||
return; | ||
} | ||
const prop = <ts.PropertyAssignment>p; | ||
if ((<any>prop).name.text === 'selector' && isSimpleTemplateString(prop.initializer)) { | ||
metadata.selector = (<any>prop).initializer.text; | ||
} | ||
}); | ||
return metadata; | ||
} | ||
|
||
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)); | ||
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); | ||
|
||
readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls): TemplateMetadata { | ||
const template_M = getTemplate(dec) | ||
.fmap(inlineTemplate => { | ||
const transformed = normalizeTransformed(Config.transformTemplate(inlineTemplate.text, null, dec)); | ||
return { | ||
template: transformed, | ||
url: null, | ||
node: inlineTemplate, | ||
}; | ||
}); | ||
|
||
if (template_M.isSomething) { | ||
return template_M.unwrap(); | ||
} else { | ||
// TODO: Refactoring this requires adding seem to fileResolver | ||
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)) { | ||
styles = styles || []; | ||
styles.push({ | ||
style: normalizeTransformed(Config.transformStyle(inlineStyle.text, null, dec)), | ||
url: null, | ||
node: inlineStyle, | ||
}); | ||
|
||
readComponentStylesMetadata(dec: ts.Decorator, external: MetadataUrls) { | ||
let styles: any[]; | ||
const inlineStyles_M = getInlineStyle(dec) | ||
.fmap(inlineStyles => { | ||
return inlineStyles.elements.map((inlineStyle: ts.Expression) => { | ||
if (isSimpleTemplateString(inlineStyle)) { | ||
return { | ||
style: normalizeTransformed(Config.transformStyle(inlineStyle.text, null, dec)), | ||
url: null, | ||
node: inlineStyle, | ||
}; | ||
} | ||
}).filter(v => !!v); | ||
}); | ||
|
||
if (inlineStyles_M.isSomething) { | ||
return inlineStyles_M.unwrap(); | ||
} else if (external.styleUrls) { | ||
// TODO: Refactoring this requires adding seem to fileResolver | ||
try { | ||
styles = <any>external.styleUrls.map((url: string) => { | ||
const style = this._fileResolver.resolve(url); | ||
const transformed = normalizeTransformed(Config.transformStyle(style, url, dec)); | ||
return { | ||
style: transformed, url, | ||
node: null | ||
}; | ||
}); | ||
} catch (e) { | ||
logger.info('Unable to read external style. ' + e.toString()); | ||
} | ||
} | ||
}); | ||
} else if (external.styleUrls) { | ||
try { | ||
styles = <any>external.styleUrls.map((url: string) => { | ||
const style = this._fileResolver.resolve(url); | ||
const transformed = normalizeTransformed(Config.transformStyle(style, url, dec)); | ||
return { | ||
style: transformed, url, | ||
node: null | ||
}; | ||
}); | ||
} catch (e) { | ||
logger.info('Unable to read external style. ' + e.toString()); | ||
} | ||
return styles; | ||
} | ||
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; | ||
|
||
readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator) { | ||
const expr = this.getDecoratorArgument(dec); | ||
const directiveMetadata = this.readDirectiveMetadata(d, dec); | ||
|
||
const external_M = expr.fmap(() => this._urlResolver.resolve(dec)); | ||
|
||
const template_M = external_M.fmap( | ||
(external) => this.readComponentTemplateMetadata(dec, external)); | ||
const style_M = external_M.fmap( | ||
(external) => this.readComponentStylesMetadata(dec, external)); | ||
|
||
return Object.assign(new ComponentMetadata(), directiveMetadata, { | ||
template: template_M.unwrap(), | ||
styles: style_M.unwrap(), | ||
}); | ||
} | ||
const external = this._urlResolver.resolve(dec); | ||
result.template = this.readComponentTemplateMetadata(dec, external); | ||
result.styles = this.readComponentStylesMetadata(dec, external); | ||
return result; | ||
} | ||
|
||
protected getDecoratorArgument(decorator: ts.Decorator): ts.ObjectLiteralExpression { | ||
const expr = <ts.CallExpression>decorator.expression; | ||
if (expr && expr.arguments && expr.arguments.length) { | ||
const arg = <ts.ObjectLiteralExpression>expr.arguments[0]; | ||
if (arg.kind === kinds.ObjectLiteralExpression && arg.properties) { | ||
return arg; | ||
} | ||
|
||
protected getDecoratorArgument(decorator: ts.Decorator): Maybe<ts.ObjectLiteralExpression> { | ||
return decoratorArgument(decorator) | ||
.bind(ifTrue(hasProperties)); | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,40 @@ | ||
import * as Lint from 'tslint'; | ||
import * as ts from 'typescript'; | ||
import {sprintf} from 'sprintf-js'; | ||
import {Ng2Walker} from './angular/ng2Walker'; | ||
import {ComponentMetadata} from './angular/metadata'; | ||
import {Failure} from './walkerFactory/walkerFactory'; | ||
import {all, validateComponent} from './walkerFactory/walkerFn'; | ||
import {Maybe, F2} from './util/function'; | ||
import {IOptions} from 'tslint'; | ||
import {Ng2Walker} from './angular/ng2Walker'; | ||
|
||
export class Rule extends Lint.Rules.AbstractRule { | ||
static FAILURE: string = 'The name of the class %s should end with the suffix %s ($$02-03$$)'; | ||
|
||
static validate(className: string, suffix: string):boolean { | ||
return className.endsWith(suffix); | ||
} | ||
static FAILURE: string = 'The name of the class %s should end with the suffix %s ($$02-03$$)'; | ||
|
||
public apply(sourceFile:ts.SourceFile):Lint.RuleFailure[] { | ||
return this.applyWithWalker( | ||
new ClassMetadataWalker(sourceFile, | ||
this.getOptions())); | ||
} | ||
} | ||
static validate(className: string, suffixList: string[]): boolean { | ||
return suffixList.some(suffix => className.endsWith(suffix)); | ||
} | ||
|
||
export class ClassMetadataWalker extends Ng2Walker { | ||
visitNg2Component(meta: ComponentMetadata) { | ||
let name = meta.controller.name; | ||
let className: string = name.text; | ||
const suffixList = this.getOptions().length > 0 ? this.getOptions() : ['Component']; | ||
let ruleInvalidate = !suffixList.some(suffix => Rule.validate(className, suffix)); | ||
if (ruleInvalidate) { | ||
this.addFailure( | ||
this.createFailure( | ||
name.getStart(), | ||
name.getWidth(), | ||
sprintf.apply(this, [Rule.FAILURE, className, suffixList]))); | ||
static walkerBuilder: F2<ts.SourceFile, IOptions, Ng2Walker> = | ||
all( | ||
validateComponent((meta: ComponentMetadata, suffixList?: string[]) => | ||
Maybe.lift(meta.controller) | ||
.fmap(controller => controller.name) | ||
.fmap(name => { | ||
const className = name.text; | ||
const _suffixList = suffixList.length > 0 ? suffixList : ['Component']; | ||
if (!Rule.validate(className, _suffixList)) { | ||
return [new Failure(name, sprintf(Rule.FAILURE, className, _suffixList))]; | ||
} | ||
}) | ||
)); | ||
|
||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
return this.applyWithWalker( | ||
Rule.walkerBuilder(sourceFile, this.getOptions()) | ||
); | ||
} | ||
super.visitNg2Component(meta); | ||
} | ||
} | ||
|
||
|
Oops, something went wrong.