Skip to content

Commit

Permalink
Merge pull request #180 from ghsyeung/refactor_maybe
Browse files Browse the repository at this point in the history
[Experimental] First attempt at replacing AST traversal with Maybe<T>
  • Loading branch information
mgechev authored Dec 25, 2016
2 parents de4390d + ba0f67f commit fe42a37
Show file tree
Hide file tree
Showing 9 changed files with 561 additions and 239 deletions.
259 changes: 126 additions & 133 deletions src/angular/metadataReader.ts
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;
}
}
54 changes: 29 additions & 25 deletions src/componentClassSuffixRule.ts
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);
}
}


Loading

0 comments on commit fe42a37

Please sign in to comment.