Skip to content
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

refactor: improve ngWalker by preventing an error when a class has no name #788

Merged
merged 3 commits into from Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/angular/metadata.ts
Expand Up @@ -44,3 +44,20 @@ export class ComponentMetadata extends DirectiveMetadata {
super(controller, decorator, selector);
}
}

export class PipeMetadata {
constructor(
public readonly controller: ts.ClassDeclaration,
public readonly decorator: ts.Decorator,
public readonly name?: string,
wKoza marked this conversation as resolved.
Show resolved Hide resolved
public readonly pure?: string
wKoza marked this conversation as resolved.
Show resolved Hide resolved
) {}
}

export class ModuleMetadata {
constructor(public readonly controller: ts.ClassDeclaration, public readonly decorator: ts.Decorator) {}
wKoza marked this conversation as resolved.
Show resolved Hide resolved
}

export class InjectableMetadata {
constructor(public readonly controller: ts.ClassDeclaration, public readonly decorator: ts.Decorator) {}
wKoza marked this conversation as resolved.
Show resolved Hide resolved
}
65 changes: 61 additions & 4 deletions src/angular/metadataReader.ts
@@ -1,12 +1,22 @@
import * as ts from 'typescript';
import { callExpression, decoratorArgument, hasProperties, withIdentifier } from '../util/astQuery';
import { callExpression, decoratorArgument, getStringInitializerFromProperty, hasProperties, withIdentifier } from '../util/astQuery';
import { ifTrue, listToMaybe, Maybe, unwrapFirst } from '../util/function';
import { logger } from '../util/logger';
import { getAnimations, getInlineStyle, getTemplate } from '../util/ngQuery';
import { getDecoratorPropertyInitializer, isStringLiteralLike, maybeNodeArray } from '../util/utils';
import { Config } from './config';
import { FileResolver } from './fileResolver/fileResolver';
import { AnimationMetadata, CodeWithSourceMap, ComponentMetadata, DirectiveMetadata, StyleMetadata, TemplateMetadata } from './metadata';
import {
AnimationMetadata,
CodeWithSourceMap,
ComponentMetadata,
DirectiveMetadata,
InjectableMetadata,
ModuleMetadata,
PipeMetadata,
StyleMetadata,
TemplateMetadata
} from './metadata';
import { AbstractResolver, MetadataUrls } from './urlResolvers/abstractResolver';
import { PathResolver } from './urlResolvers/pathResolver';
import { UrlResolver } from './urlResolvers/urlResolver';
Expand All @@ -26,7 +36,7 @@ export class MetadataReader {
this.urlResolver = this.urlResolver || new UrlResolver(new PathResolver());
}

read(d: ts.ClassDeclaration): DirectiveMetadata | undefined {
read(d: ts.ClassDeclaration): DirectiveMetadata | ComponentMetadata | PipeMetadata | ModuleMetadata | InjectableMetadata | undefined {
const componentMetadata = unwrapFirst<ComponentMetadata | undefined>(
maybeNodeArray(d.decorators!).map(dec => {
return Maybe.lift(dec)
Expand All @@ -45,7 +55,34 @@ export class MetadataReader {
)
);

return directiveMetadata || componentMetadata || undefined;
const pipeMetadata = unwrapFirst<PipeMetadata | undefined>(
maybeNodeArray(d.decorators!).map(dec =>
wKoza marked this conversation as resolved.
Show resolved Hide resolved
Maybe.lift(dec)
.bind(callExpression)
.bind(withIdentifier('Pipe') as any)
.fmap(() => this.readPipeMetadata(d, dec))
)
);

const moduleMetadata = unwrapFirst<ModuleMetadata | undefined>(
maybeNodeArray(d.decorators!).map(dec =>
wKoza marked this conversation as resolved.
Show resolved Hide resolved
Maybe.lift(dec)
.bind(callExpression)
.bind(withIdentifier('NgModule') as any)
.fmap(() => this.readModuleMetadata(d, dec))
)
);

const injectableMetadata = unwrapFirst<InjectableMetadata | undefined>(
maybeNodeArray(d.decorators!).map(dec =>
wKoza marked this conversation as resolved.
Show resolved Hide resolved
Maybe.lift(dec)
.bind(callExpression)
.bind(withIdentifier('Injectable') as any)
.fmap(() => this.readInjectableMetadata(d, dec))
)
);

return directiveMetadata || componentMetadata || pipeMetadata || moduleMetadata || injectableMetadata || undefined;
wKoza marked this conversation as resolved.
Show resolved Hide resolved
}

protected readDirectiveMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
Expand All @@ -55,6 +92,26 @@ export class MetadataReader {
return new DirectiveMetadata(d, dec, selector);
}

protected readPipeMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
const name = this.getDecoratorArgument(dec)
.bind(expr => getStringInitializerFromProperty('name', expr!.properties))
wKoza marked this conversation as resolved.
Show resolved Hide resolved
.fmap(initializer => initializer!.text);

const pure = this.getDecoratorArgument(dec)
.bind(expr => getStringInitializerFromProperty('pure', expr!.properties))
wKoza marked this conversation as resolved.
Show resolved Hide resolved
.fmap(initializer => initializer!.text);
wKoza marked this conversation as resolved.
Show resolved Hide resolved

return new PipeMetadata(d, dec, name.unwrap(), pure ? pure.unwrap() : undefined);
}

protected readModuleMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
return new ModuleMetadata(d, dec);
}

protected readInjectableMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
return new InjectableMetadata(d, dec);
}

protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): ComponentMetadata {
const expr = this.getDecoratorArgument(dec);
const directiveMetadata = this.readDirectiveMetadata(d, dec);
Expand Down
55 changes: 29 additions & 26 deletions src/angular/ngWalker.ts
Expand Up @@ -2,9 +2,9 @@ import * as compiler from '@angular/compiler';
import * as Lint from 'tslint';
import * as ts from 'typescript';
import { logger } from '../util/logger';
import { getDecoratorName, maybeNodeArray } from '../util/utils';
import { getClassName, getDecoratorName, maybeNodeArray } from '../util/utils';
import { Config } from './config';
import { ComponentMetadata, DirectiveMetadata, StyleMetadata } from './metadata';
import { ComponentMetadata, DirectiveMetadata, InjectableMetadata, ModuleMetadata, PipeMetadata, StyleMetadata } from './metadata';
import { MetadataReader } from './metadataReader';
import { ngWalkerFactoryUtils } from './ngWalkerFactoryUtils';
import { BasicCssAstVisitor, CssAstVisitorCtrl } from './styles/basicCssAstVisitor';
Expand Down Expand Up @@ -61,11 +61,19 @@ export class NgWalker extends Lint.RuleWalker {
}

visitClassDeclaration(declaration: ts.ClassDeclaration) {
const metadata = this._metadataReader!.read(declaration);
if (metadata instanceof ComponentMetadata) {
this.visitNgComponent(metadata);
} else if (metadata instanceof DirectiveMetadata) {
this.visitNgDirective(metadata);
if (this.hasClassName(declaration)) {
const metadata = this._metadataReader!.read(declaration);
if (metadata instanceof ComponentMetadata) {
this.visitNgComponent(metadata);
} else if (metadata instanceof DirectiveMetadata) {
this.visitNgDirective(metadata);
} else if (metadata instanceof PipeMetadata) {
this.visitNgPipe(metadata);
} else if (metadata instanceof ModuleMetadata) {
this.visitNgModule(metadata);
} else if (metadata instanceof InjectableMetadata) {
this.visitNgInjectable(metadata);
}
}
maybeNodeArray(ts.createNodeArray(declaration.decorators)).forEach(this.visitClassDecorator.bind(this));
super.visitClassDeclaration(declaration);
Expand Down Expand Up @@ -115,22 +123,6 @@ export class NgWalker extends Lint.RuleWalker {
}
}

protected visitClassDecorator(decorator: ts.Decorator) {
let name = getDecoratorName(decorator);

if (name === 'Injectable') {
this.visitNgInjectable(decorator.parent as ts.ClassDeclaration, decorator);
}

if (name === 'Pipe') {
this.visitNgPipe(decorator.parent as ts.ClassDeclaration, decorator);
}

if (name === 'NgModule') {
this.visitNgModule(decorator);
}
}

protected visitNgComponent(metadata: ComponentMetadata) {
const { styles = [] } = metadata;

Expand Down Expand Up @@ -165,13 +157,15 @@ export class NgWalker extends Lint.RuleWalker {
}
}

protected visitNgModule(decorator: ts.Decorator) {}
protected visitClassDecorator(decorator: ts.Decorator) {}

protected visitNgModule(metadata: ModuleMetadata) {}

protected visitNgDirective(metadata: DirectiveMetadata) {}

protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) {}
protected visitNgPipe(metadata: DirectiveMetadata) {}
wKoza marked this conversation as resolved.
Show resolved Hide resolved

protected visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) {}
protected visitNgInjectable(metadata: InjectableMetadata) {}

protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {}

Expand Down Expand Up @@ -237,4 +231,13 @@ export class NgWalker extends Lint.RuleWalker {

return sf;
}

private hasClassName(node: ts.Decorator | ts.ClassDeclaration) {
if (ts.isDecorator(node) && !getClassName(node.parent as ts.ClassDeclaration)) {
wKoza marked this conversation as resolved.
Show resolved Hide resolved
return false;
} else if (ts.isClassDeclaration(node) && !getClassName(node)) {
return false;
}
return true;
}
}
17 changes: 8 additions & 9 deletions src/contextualLifecycleRule.ts
Expand Up @@ -14,6 +14,7 @@ import {
MetadataTypeKeys,
MetadataTypes
} from './util/utils';
import { InjectableMetadata, PipeMetadata } from './angular';

interface FailureParameters {
readonly className: string;
Expand Down Expand Up @@ -45,20 +46,18 @@ export class Rule extends AbstractRule {
}

class ContextualLifecycleWalker extends NgWalker {
protected visitNgInjectable(controller: ClassDeclaration, decorator: Decorator): void {
this.validateDecorator(controller, decorator, METADATA_TYPE_LIFECYCLE_MAPPER.Injectable);
super.visitNgInjectable(controller, decorator);
visitNgInjectable(metadata: InjectableMetadata): void {
wKoza marked this conversation as resolved.
Show resolved Hide resolved
this.validateDecorator(metadata.controller, metadata.decorator, METADATA_TYPE_LIFECYCLE_MAPPER.Injectable);
super.visitNgInjectable(metadata);
}

protected visitNgPipe(controller: ClassDeclaration, decorator: Decorator): void {
this.validateDecorator(controller, decorator, METADATA_TYPE_LIFECYCLE_MAPPER.Pipe);
super.visitNgPipe(controller, decorator);
visitNgPipe(metadata: PipeMetadata): void {
this.validateDecorator(metadata.controller, metadata.decorator, METADATA_TYPE_LIFECYCLE_MAPPER.Pipe);
super.visitNgPipe(metadata);
}

private validateDecorator(controller: ClassDeclaration, decorator: Decorator, allowedMethods: ReadonlySet<LifecycleMethodKeys>): void {
wKoza marked this conversation as resolved.
Show resolved Hide resolved
const className = getClassName(controller);

if (!className) return;
wKoza marked this conversation as resolved.
Show resolved Hide resolved
const className = getClassName(controller)!;

const metadataType = getDecoratorName(decorator);

Expand Down
15 changes: 7 additions & 8 deletions src/noPipeImpureRule.ts
Expand Up @@ -4,6 +4,7 @@ import { AbstractRule } from 'tslint/lib/rules';
import { ClassDeclaration, Decorator, SourceFile, SyntaxKind } from 'typescript';
import { NgWalker } from './angular/ngWalker';
import { getClassName, getDecoratorPropertyInitializer } from './util/utils';
import { PipeMetadata } from './angular';

interface FailureParameters {
readonly className: string;
Expand Down Expand Up @@ -31,13 +32,13 @@ export class Rule extends AbstractRule {
}

export class ClassMetadataWalker extends NgWalker {
protected visitNgPipe(controller: ClassDeclaration, decorator: Decorator): void {
this.validatePipe(controller, decorator);
super.visitNgPipe(controller, decorator);
protected visitNgPipe(metadata: PipeMetadata): void {
this.validatePipe(metadata);
super.visitNgPipe(metadata);
}

private validatePipe(controller: ClassDeclaration, decorator: Decorator): void {
const pureExpression = getDecoratorPropertyInitializer(decorator, 'pure');
private validatePipe(metadata: PipeMetadata): void {
const pureExpression = getDecoratorPropertyInitializer(metadata.decorator, 'pure');
wKoza marked this conversation as resolved.
Show resolved Hide resolved

if (!pureExpression) return;

Expand All @@ -46,9 +47,7 @@ export class ClassMetadataWalker extends NgWalker {

if (!parentExpression || isNotFalseLiteral) return;

const className = getClassName(controller);

if (!className) return;
wKoza marked this conversation as resolved.
Show resolved Hide resolved
const className = getClassName(metadata.controller)!;

const failure = getFailureMessage({ className });

Expand Down
9 changes: 5 additions & 4 deletions src/pipePrefixRule.ts
Expand Up @@ -4,6 +4,7 @@ import * as ts from 'typescript';
import { NgWalker } from './angular/ngWalker';
import { SelectorValidator } from './util/selectorValidator';
import { getDecoratorArgument } from './util/utils';
import { PipeMetadata } from './angular';

export class Rule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata = {
Expand Down Expand Up @@ -69,10 +70,10 @@ export class ClassMetadataWalker extends NgWalker {
super(sourceFile, rule.getOptions());
}

protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) {
let className = controller.name!.text;
this.validateProperties(className, decorator);
super.visitNgPipe(controller, decorator);
protected visitNgPipe(metadata: PipeMetadata) {
let className = metadata.controller.name!.text;
this.validateProperties(className, metadata.decorator);
super.visitNgPipe(metadata);
}

private validateProperties(className: string, pipe: ts.Decorator) {
Expand Down