-
Notifications
You must be signed in to change notification settings - Fork 235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Experimental] First attempt at replacing AST traversal with Maybe<T> #180
Changes from all commits
d208a91
247279d
3e24bb0
531b140
ba0f67f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can first apply |
||
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I notice that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like an issue. |
||
(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; | ||
} | ||
} |
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); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks beautiful!