Skip to content

Commit 17c0fe2

Browse files
wKozamgechev
authored andcommitted
refactor: improve ngWalker by preventing an error when a class has no name (#788)
* improve ngWalker by preventing an error when a class has no name. * fixup! improve ngWalker by preventing an error when a class has no name. * fixup! improve ngWalker by preventing an error when a class has no name.
1 parent 31b2b6a commit 17c0fe2

11 files changed

+222
-102
lines changed

src/angular/metadata.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,35 @@ export interface TemplateMetadata extends PropertyMetadata {
2525
}
2626

2727
export class DirectiveMetadata {
28-
constructor(
29-
public readonly controller: ts.ClassDeclaration,
30-
public readonly decorator: ts.Decorator,
31-
public readonly selector?: string
32-
) {}
28+
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator, readonly selector?: string) {}
3329
}
3430

3531
export class ComponentMetadata extends DirectiveMetadata {
3632
constructor(
37-
public readonly controller: ts.ClassDeclaration,
38-
public readonly decorator: ts.Decorator,
39-
public readonly selector?: string,
40-
public readonly animations?: (AnimationMetadata | undefined)[],
41-
public readonly styles?: (StyleMetadata | undefined)[],
42-
public readonly template?: TemplateMetadata
33+
readonly controller: ts.ClassDeclaration,
34+
readonly decorator: ts.Decorator,
35+
readonly selector?: string,
36+
readonly animations?: (AnimationMetadata | undefined)[],
37+
readonly styles?: (StyleMetadata | undefined)[],
38+
readonly template?: TemplateMetadata
4339
) {
4440
super(controller, decorator, selector);
4541
}
4642
}
43+
44+
export class PipeMetadata {
45+
constructor(
46+
readonly controller: ts.ClassDeclaration,
47+
readonly decorator: ts.Decorator,
48+
readonly name?: string,
49+
readonly pure?: ts.BooleanLiteral
50+
) {}
51+
}
52+
53+
export class ModuleMetadata {
54+
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator) {}
55+
}
56+
57+
export class InjectableMetadata {
58+
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator) {}
59+
}

src/angular/metadataReader.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@ import { callExpression, decoratorArgument, hasProperties, withIdentifier } from
33
import { ifTrue, listToMaybe, Maybe, unwrapFirst } from '../util/function';
44
import { logger } from '../util/logger';
55
import { getAnimations, getInlineStyle, getTemplate } from '../util/ngQuery';
6-
import { getDecoratorPropertyInitializer, isStringLiteralLike, maybeNodeArray } from '../util/utils';
6+
import { getDecoratorPropertyInitializer, isBooleanLiteralLike, isStringLiteralLike, maybeNodeArray } from '../util/utils';
77
import { Config } from './config';
88
import { FileResolver } from './fileResolver/fileResolver';
9-
import { AnimationMetadata, CodeWithSourceMap, ComponentMetadata, DirectiveMetadata, StyleMetadata, TemplateMetadata } from './metadata';
9+
import {
10+
AnimationMetadata,
11+
CodeWithSourceMap,
12+
ComponentMetadata,
13+
DirectiveMetadata,
14+
InjectableMetadata,
15+
ModuleMetadata,
16+
PipeMetadata,
17+
StyleMetadata,
18+
TemplateMetadata
19+
} from './metadata';
1020
import { AbstractResolver, MetadataUrls } from './urlResolvers/abstractResolver';
1121
import { PathResolver } from './urlResolvers/pathResolver';
1222
import { UrlResolver } from './urlResolvers/urlResolver';
@@ -26,9 +36,9 @@ export class MetadataReader {
2636
this.urlResolver = this.urlResolver || new UrlResolver(new PathResolver());
2737
}
2838

29-
read(d: ts.ClassDeclaration): DirectiveMetadata | undefined {
39+
read(d: ts.ClassDeclaration): DirectiveMetadata | ComponentMetadata | PipeMetadata | ModuleMetadata | InjectableMetadata | undefined {
3040
const componentMetadata = unwrapFirst<ComponentMetadata | undefined>(
31-
maybeNodeArray(d.decorators!).map(dec => {
41+
maybeNodeArray(ts.createNodeArray(d.decorators)).map(dec => {
3242
return Maybe.lift(dec)
3343
.bind(callExpression)
3444
.bind(withIdentifier('Component') as any)
@@ -37,15 +47,42 @@ export class MetadataReader {
3747
);
3848

3949
const directiveMetadata = unwrapFirst<DirectiveMetadata | undefined>(
40-
maybeNodeArray(d.decorators!).map(dec =>
50+
maybeNodeArray(ts.createNodeArray(d.decorators)).map(dec =>
4151
Maybe.lift(dec)
4252
.bind(callExpression)
4353
.bind(withIdentifier('Directive') as any)
4454
.fmap(() => this.readDirectiveMetadata(d, dec))
4555
)
4656
);
4757

48-
return directiveMetadata || componentMetadata || undefined;
58+
const pipeMetadata = unwrapFirst<PipeMetadata | undefined>(
59+
maybeNodeArray(ts.createNodeArray(d.decorators)).map(dec =>
60+
Maybe.lift(dec)
61+
.bind(callExpression)
62+
.bind(withIdentifier('Pipe') as any)
63+
.fmap(() => this.readPipeMetadata(d, dec))
64+
)
65+
);
66+
67+
const moduleMetadata = unwrapFirst<ModuleMetadata | undefined>(
68+
maybeNodeArray(ts.createNodeArray(d.decorators)).map(dec =>
69+
Maybe.lift(dec)
70+
.bind(callExpression)
71+
.bind(withIdentifier('NgModule') as any)
72+
.fmap(() => this.readModuleMetadata(d, dec))
73+
)
74+
);
75+
76+
const injectableMetadata = unwrapFirst<InjectableMetadata | undefined>(
77+
maybeNodeArray(ts.createNodeArray(d.decorators)).map(dec =>
78+
Maybe.lift(dec)
79+
.bind(callExpression)
80+
.bind(withIdentifier('Injectable') as any)
81+
.fmap(() => this.readInjectableMetadata(d, dec))
82+
)
83+
);
84+
85+
return directiveMetadata || componentMetadata || pipeMetadata || moduleMetadata || injectableMetadata;
4986
}
5087

5188
protected readDirectiveMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
@@ -55,6 +92,24 @@ export class MetadataReader {
5592
return new DirectiveMetadata(d, dec, selector);
5693
}
5794

95+
protected readPipeMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
96+
const nameExpression = getDecoratorPropertyInitializer(dec, 'name');
97+
const name = nameExpression && isStringLiteralLike(nameExpression) ? nameExpression.text : undefined;
98+
99+
const pureExpression = getDecoratorPropertyInitializer(dec, 'pure');
100+
const pure = pureExpression && isBooleanLiteralLike(pureExpression) ? pureExpression : undefined;
101+
102+
return new PipeMetadata(d, dec, name, pure);
103+
}
104+
105+
protected readModuleMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
106+
return new ModuleMetadata(d, dec);
107+
}
108+
109+
protected readInjectableMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
110+
return new InjectableMetadata(d, dec);
111+
}
112+
58113
protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): ComponentMetadata {
59114
const expr = this.getDecoratorArgument(dec);
60115
const directiveMetadata = this.readDirectiveMetadata(d, dec);

src/angular/ngWalker.ts

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import * as compiler from '@angular/compiler';
22
import * as Lint from 'tslint';
33
import * as ts from 'typescript';
44
import { logger } from '../util/logger';
5-
import { getDecoratorName, maybeNodeArray } from '../util/utils';
5+
import { getClassName, getDecoratorName, maybeNodeArray } from '../util/utils';
66
import { Config } from './config';
7-
import { ComponentMetadata, DirectiveMetadata, StyleMetadata } from './metadata';
7+
import { ComponentMetadata, DirectiveMetadata, InjectableMetadata, ModuleMetadata, PipeMetadata, StyleMetadata } from './metadata';
88
import { MetadataReader } from './metadataReader';
99
import { ngWalkerFactoryUtils } from './ngWalkerFactoryUtils';
1010
import { BasicCssAstVisitor, CssAstVisitorCtrl } from './styles/basicCssAstVisitor';
@@ -61,11 +61,19 @@ export class NgWalker extends Lint.RuleWalker {
6161
}
6262

6363
visitClassDeclaration(declaration: ts.ClassDeclaration) {
64-
const metadata = this._metadataReader!.read(declaration);
65-
if (metadata instanceof ComponentMetadata) {
66-
this.visitNgComponent(metadata);
67-
} else if (metadata instanceof DirectiveMetadata) {
68-
this.visitNgDirective(metadata);
64+
if (this.hasClassName(declaration)) {
65+
const metadata = this._metadataReader!.read(declaration);
66+
if (metadata instanceof ComponentMetadata) {
67+
this.visitNgComponent(metadata);
68+
} else if (metadata instanceof DirectiveMetadata) {
69+
this.visitNgDirective(metadata);
70+
} else if (metadata instanceof PipeMetadata) {
71+
this.visitNgPipe(metadata);
72+
} else if (metadata instanceof ModuleMetadata) {
73+
this.visitNgModule(metadata);
74+
} else if (metadata instanceof InjectableMetadata) {
75+
this.visitNgInjectable(metadata);
76+
}
6977
}
7078
maybeNodeArray(ts.createNodeArray(declaration.decorators)).forEach(this.visitClassDecorator.bind(this));
7179
super.visitClassDeclaration(declaration);
@@ -115,22 +123,6 @@ export class NgWalker extends Lint.RuleWalker {
115123
}
116124
}
117125

118-
protected visitClassDecorator(decorator: ts.Decorator) {
119-
let name = getDecoratorName(decorator);
120-
121-
if (name === 'Injectable') {
122-
this.visitNgInjectable(decorator.parent as ts.ClassDeclaration, decorator);
123-
}
124-
125-
if (name === 'Pipe') {
126-
this.visitNgPipe(decorator.parent as ts.ClassDeclaration, decorator);
127-
}
128-
129-
if (name === 'NgModule') {
130-
this.visitNgModule(decorator);
131-
}
132-
}
133-
134126
protected visitNgComponent(metadata: ComponentMetadata) {
135127
const { styles = [] } = metadata;
136128

@@ -165,13 +157,15 @@ export class NgWalker extends Lint.RuleWalker {
165157
}
166158
}
167159

168-
protected visitNgModule(decorator: ts.Decorator) {}
160+
protected visitClassDecorator(decorator: ts.Decorator) {}
161+
162+
protected visitNgModule(metadata: ModuleMetadata) {}
169163

170164
protected visitNgDirective(metadata: DirectiveMetadata) {}
171165

172-
protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) {}
166+
protected visitNgPipe(metadata: PipeMetadata) {}
173167

174-
protected visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) {}
168+
protected visitNgInjectable(metadata: InjectableMetadata) {}
175169

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

@@ -237,4 +231,8 @@ export class NgWalker extends Lint.RuleWalker {
237231

238232
return sf;
239233
}
234+
235+
private hasClassName(node: ts.Decorator | ts.ClassDeclaration) {
236+
return (ts.isDecorator(node) && getClassName(node.parent)) || (ts.isClassDeclaration(node) && getClassName(node));
237+
}
240238
}

src/contextualLifecycleRule.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
MetadataTypeKeys,
1515
MetadataTypes
1616
} from './util/utils';
17+
import { InjectableMetadata, PipeMetadata } from './angular';
1718

1819
interface FailureParameters {
1920
readonly className: string;
@@ -45,26 +46,24 @@ export class Rule extends AbstractRule {
4546
}
4647

4748
class ContextualLifecycleWalker extends NgWalker {
48-
protected visitNgInjectable(controller: ClassDeclaration, decorator: Decorator): void {
49-
this.validateDecorator(controller, decorator, METADATA_TYPE_LIFECYCLE_MAPPER.Injectable);
50-
super.visitNgInjectable(controller, decorator);
49+
protected visitNgInjectable(metadata: InjectableMetadata): void {
50+
this.validateDecorator(metadata, METADATA_TYPE_LIFECYCLE_MAPPER.Injectable);
51+
super.visitNgInjectable(metadata);
5152
}
5253

53-
protected visitNgPipe(controller: ClassDeclaration, decorator: Decorator): void {
54-
this.validateDecorator(controller, decorator, METADATA_TYPE_LIFECYCLE_MAPPER.Pipe);
55-
super.visitNgPipe(controller, decorator);
54+
protected visitNgPipe(metadata: PipeMetadata): void {
55+
this.validateDecorator(metadata, METADATA_TYPE_LIFECYCLE_MAPPER.Pipe);
56+
super.visitNgPipe(metadata);
5657
}
5758

58-
private validateDecorator(controller: ClassDeclaration, decorator: Decorator, allowedMethods: ReadonlySet<LifecycleMethodKeys>): void {
59-
const className = getClassName(controller);
59+
private validateDecorator(metadata: PipeMetadata, allowedMethods: ReadonlySet<LifecycleMethodKeys>): void {
60+
const className = getClassName(metadata.controller)!;
6061

61-
if (!className) return;
62-
63-
const metadataType = getDecoratorName(decorator);
62+
const metadataType = getDecoratorName(metadata.decorator);
6463

6564
if (!metadataType || !isMetadataType(metadataType)) return;
6665

67-
for (const member of controller.members) {
66+
for (const member of metadata.controller.members) {
6867
const { name: memberName } = member;
6968

7069
if (!memberName) continue;

src/noOutputRenameRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const getFailureMessage = (): string => {
2929
export class OutputMetadataWalker extends NgWalker {
3030
private directiveSelectors!: ReadonlySet<DirectiveMetadata['selector']>;
3131

32-
visitNgDirective(metadata: DirectiveMetadata): void {
32+
protected visitNgDirective(metadata: DirectiveMetadata): void {
3333
this.directiveSelectors = new Set((metadata.selector || '').replace(/[\[\]\s]/g, '').split(','));
3434
super.visitNgDirective(metadata);
3535
}

src/noPipeImpureRule.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AbstractRule } from 'tslint/lib/rules';
44
import { ClassDeclaration, Decorator, SourceFile, SyntaxKind } from 'typescript';
55
import { NgWalker } from './angular/ngWalker';
66
import { getClassName, getDecoratorPropertyInitializer } from './util/utils';
7+
import { PipeMetadata } from './angular';
78

89
interface FailureParameters {
910
readonly className: string;
@@ -31,27 +32,18 @@ export class Rule extends AbstractRule {
3132
}
3233

3334
export class ClassMetadataWalker extends NgWalker {
34-
protected visitNgPipe(controller: ClassDeclaration, decorator: Decorator): void {
35-
this.validatePipe(controller, decorator);
36-
super.visitNgPipe(controller, decorator);
35+
protected visitNgPipe(metadata: PipeMetadata): void {
36+
this.validatePipe(metadata);
37+
super.visitNgPipe(metadata);
3738
}
3839

39-
private validatePipe(controller: ClassDeclaration, decorator: Decorator): void {
40-
const pureExpression = getDecoratorPropertyInitializer(decorator, 'pure');
40+
private validatePipe(metadata: PipeMetadata): void {
41+
if (!metadata.pure || metadata.pure.kind !== SyntaxKind.FalseKeyword) return;
4142

42-
if (!pureExpression) return;
43-
44-
const { parent: parentExpression } = pureExpression;
45-
const isNotFalseLiteral = pureExpression.kind !== SyntaxKind.FalseKeyword;
46-
47-
if (!parentExpression || isNotFalseLiteral) return;
48-
49-
const className = getClassName(controller);
50-
51-
if (!className) return;
43+
const className = getClassName(metadata.controller)!;
5244

5345
const failure = getFailureMessage({ className });
5446

55-
this.addFailureAtNode(parentExpression, failure);
47+
this.addFailureAtNode(metadata.pure, failure);
5648
}
5749
}

src/pipePrefixRule.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as ts from 'typescript';
44
import { NgWalker } from './angular/ngWalker';
55
import { SelectorValidator } from './util/selectorValidator';
66
import { getDecoratorArgument } from './util/utils';
7+
import { PipeMetadata } from './angular';
78

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

72-
protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) {
73-
let className = controller.name!.text;
74-
this.validateProperties(className, decorator);
75-
super.visitNgPipe(controller, decorator);
73+
protected visitNgPipe(metadata: PipeMetadata) {
74+
let className = metadata.controller.name!.text;
75+
this.validateProperties(className, metadata.decorator);
76+
super.visitNgPipe(metadata);
7677
}
7778

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

src/util/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ObjectLiteralExpression,
2020
SourceFile,
2121
StringLiteral,
22+
BooleanLiteral,
2223
SyntaxKind
2324
} from 'typescript';
2425
import { getDeclaredMethods } from './classDeclarationUtils';
@@ -202,6 +203,9 @@ export const isSameLine = (sourceFile: SourceFile, pos1: number, pos2: number):
202203
export const isStringLiteralLike = (node: Node): node is StringLiteral | NoSubstitutionTemplateLiteral =>
203204
isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node);
204205

206+
export const isBooleanLiteralLike = (node: Node): node is BooleanLiteral =>
207+
node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.TrueKeyword;
208+
205209
export const maybeNodeArray = <T extends Node>(nodes: NodeArray<T>): ReadonlyArray<T> => nodes || [];
206210

207211
// Regex below matches any Unicode word and compatible with ES5. In ES2018 the same result

0 commit comments

Comments
 (0)