Skip to content

Commit 25f3e16

Browse files
rafaelss95mgechev
authored andcommitted
feat(rule): add option in max-inline-declarations to limit animations lines (#569)
1 parent dfe8c76 commit 25f3e16

File tree

5 files changed

+198
-60
lines changed

5 files changed

+198
-60
lines changed

src/angular/metadata.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as ts from 'typescript';
21
import { RawSourceMap } from 'source-map';
2+
import * as ts from 'typescript';
33

44
export interface CodeWithSourceMap {
55
code: string;
@@ -12,6 +12,10 @@ interface PropertyMetadata {
1212
url?: string;
1313
}
1414

15+
export interface AnimationMetadata extends PropertyMetadata {
16+
animation: CodeWithSourceMap;
17+
}
18+
1519
export interface StyleMetadata extends PropertyMetadata {
1620
style: CodeWithSourceMap;
1721
}
@@ -27,6 +31,7 @@ export class DirectiveMetadata {
2731
}
2832

2933
export class ComponentMetadata extends DirectiveMetadata {
34+
animations!: AnimationMetadata[];
3035
styles!: StyleMetadata[];
3136
template!: TemplateMetadata;
3237
}

src/angular/metadataReader.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import {
99
} from '../util/astQuery';
1010
import { ifTrue, listToMaybe, Maybe, unwrapFirst } from '../util/function';
1111
import { logger } from '../util/logger';
12-
import { getInlineStyle, getTemplate } from '../util/ngQuery';
12+
import { getAnimations, getInlineStyle, getTemplate } from '../util/ngQuery';
1313
import { maybeNodeArray } from '../util/utils';
1414
import { Config } from './config';
1515
import { FileResolver } from './fileResolver/fileResolver';
16-
import { CodeWithSourceMap, ComponentMetadata, DirectiveMetadata, StyleMetadata, TemplateMetadata } from './metadata';
16+
import { AnimationMetadata, CodeWithSourceMap, ComponentMetadata, DirectiveMetadata, StyleMetadata, TemplateMetadata } from './metadata';
1717
import { AbstractResolver, MetadataUrls } from './urlResolvers/abstractResolver';
1818
import { PathResolver } from './urlResolvers/pathResolver';
1919
import { UrlResolver } from './urlResolvers/urlResolver';
@@ -71,10 +71,12 @@ export class MetadataReader {
7171
const expr = this.getDecoratorArgument(dec);
7272
const directiveMetadata = this.readDirectiveMetadata(d, dec);
7373
const external_M = expr.fmap(() => this._urlResolver!.resolve(dec));
74+
const animations_M = external_M.bind(external => this.readComponentAnimationsMetadata(dec, external!));
7475
const style_M = external_M.bind(external => this.readComponentStylesMetadata(dec, external!));
7576
const template_M = external_M.bind(external => this.readComponentTemplateMetadata(dec, external!));
7677

7778
return Object.assign(new ComponentMetadata(), directiveMetadata, {
79+
animations: animations_M.unwrap(),
7880
styles: style_M.unwrap(),
7981
template: template_M.unwrap()
8082
});
@@ -84,6 +86,17 @@ export class MetadataReader {
8486
return decoratorArgument(decorator).bind(ifTrue(hasProperties));
8587
}
8688

89+
protected readComponentAnimationsMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe<AnimationMetadata[] | undefined> {
90+
return getAnimations(dec).fmap(inlineAnimations =>
91+
inlineAnimations!.elements
92+
.filter(inlineAnimation => isSimpleTemplateString(inlineAnimation))
93+
.map<AnimationMetadata>(inlineAnimation => ({
94+
animation: normalizeTransformed({ code: (inlineAnimation as ts.StringLiteral | ts.NoSubstitutionTemplateLiteral).text }),
95+
node: inlineAnimation as ts.Node
96+
}))
97+
);
98+
}
99+
87100
protected readComponentTemplateMetadata(dec: ts.Decorator, external: MetadataUrls): Maybe<TemplateMetadata | undefined> {
88101
// Resolve Inline template
89102
return getTemplate(dec)

src/maxInlineDeclarationsRule.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@ import { SourceFile } from 'typescript/lib/typescript';
44
import { CodeWithSourceMap, ComponentMetadata } from './angular/metadata';
55
import { NgWalker } from './angular/ngWalker';
66

7+
const DEFAULT_ANIMATIONS_LIMIT: number = 15;
78
const DEFAULT_STYLES_LIMIT: number = 3;
89
const DEFAULT_TEMPLATE_LIMIT: number = 3;
10+
const OPTION_ANIMATIONS = 'animations';
911
const OPTION_STYLES = 'styles';
1012
const OPTION_TEMPLATE = 'template';
1113

1214
export class Rule extends Rules.AbstractRule {
1315
static readonly metadata: IRuleMetadata = {
1416
description: 'Disallows having too many lines in inline template and styles. Forces separate template or styles file creation.',
1517
descriptionDetails: 'See more at https://angular.io/guide/styleguide#style-05-04.',
16-
optionExamples: [true, [true, { [OPTION_STYLES]: 8, [OPTION_TEMPLATE]: 5 }]],
18+
optionExamples: [true, [true, { [OPTION_ANIMATIONS]: 20, [OPTION_STYLES]: 8, [OPTION_TEMPLATE]: 5 }]],
1719
options: {
1820
items: {
1921
properties: {
22+
[OPTION_ANIMATIONS]: {
23+
type: 'number'
24+
},
2025
[OPTION_STYLES]: {
2126
type: 'number'
2227
},
@@ -31,7 +36,8 @@ export class Rule extends Rules.AbstractRule {
3136
type: 'array'
3237
},
3338
optionsDescription: Utils.dedent`
34-
It can take an optional object with the properties '${OPTION_STYLES}' and '${OPTION_TEMPLATE}':
39+
It can take an optional object with the properties '${OPTION_ANIMATIONS}', '${OPTION_STYLES}' and '${OPTION_TEMPLATE}':
40+
* \`${OPTION_ANIMATIONS}\` - number > 0 defining the maximum allowed inline lines for animations. Defaults to ${DEFAULT_ANIMATIONS_LIMIT}.
3541
* \`${OPTION_STYLES}\` - number > 0 defining the maximum allowed inline lines for styles. Defaults to ${DEFAULT_STYLES_LIMIT}.
3642
* \`${OPTION_TEMPLATE}\` - number > 0 defining the maximum allowed inline lines for template. Defaults to ${DEFAULT_TEMPLATE_LIMIT}.
3743
`,
@@ -60,14 +66,17 @@ export class Rule extends Rules.AbstractRule {
6066
}
6167
}
6268

63-
type PropertyType = 'styles' | 'template';
64-
69+
type PropertyType = 'animations' | 'styles' | 'template';
6570
export type PropertyPair = { [key in PropertyType]?: number };
6671

6772
const generateFailure = (type: PropertyType, limit: number, value: number): string => {
6873
return sprintf(Rule.FAILURE_STRING, type, limit, value);
6974
};
7075

76+
export const getAnimationsFailure = (value: number, limit = DEFAULT_ANIMATIONS_LIMIT): string => {
77+
return generateFailure(OPTION_ANIMATIONS, limit, value);
78+
};
79+
7180
export const getStylesFailure = (value: number, limit = DEFAULT_STYLES_LIMIT): string => {
7281
return generateFailure(OPTION_STYLES, limit, value);
7382
};
@@ -77,29 +86,54 @@ export const getTemplateFailure = (value: number, limit = DEFAULT_TEMPLATE_LIMIT
7786
};
7887

7988
export class MaxInlineDeclarationsWalker extends NgWalker {
89+
private readonly animationsLinesLimit = DEFAULT_ANIMATIONS_LIMIT;
8090
private readonly stylesLinesLimit = DEFAULT_STYLES_LIMIT;
8191
private readonly templateLinesLimit = DEFAULT_TEMPLATE_LIMIT;
8292
private readonly newLineRegExp = /\r\n|\r|\n/;
8393

8494
constructor(sourceFile: SourceFile, options: IOptions) {
8595
super(sourceFile, options);
8696

87-
const { styles = -1, template = -1 } = (options.ruleArguments[0] || []) as PropertyPair;
97+
const { animations = -1, styles = -1, template = -1 } = (options.ruleArguments[0] || []) as PropertyPair;
8898

99+
this.animationsLinesLimit = animations > -1 ? animations : this.animationsLinesLimit;
89100
this.stylesLinesLimit = styles > -1 ? styles : this.stylesLinesLimit;
90101
this.templateLinesLimit = template > -1 ? template : this.templateLinesLimit;
91102
}
92103

93104
protected visitNgComponent(metadata: ComponentMetadata): void {
94-
this.validateInlineTemplate(metadata);
105+
this.validateInlineAnimations(metadata);
95106
this.validateInlineStyles(metadata);
107+
this.validateInlineTemplate(metadata);
96108
super.visitNgComponent(metadata);
97109
}
98110

99111
private getLinesCount(source: CodeWithSourceMap['source']): number {
100112
return source!.trim().split(this.newLineRegExp).length;
101113
}
102114

115+
private getInlineAnimationsLinesCount(metadata: ComponentMetadata): number {
116+
return (metadata.animations || []).reduce((previousValue, currentValue) => {
117+
previousValue += this.getLinesCount(currentValue.animation.source);
118+
119+
return previousValue;
120+
}, 0);
121+
}
122+
123+
private validateInlineAnimations(metadata: ComponentMetadata): void {
124+
const linesCount = this.getInlineAnimationsLinesCount(metadata);
125+
126+
if (linesCount <= this.animationsLinesLimit) {
127+
return;
128+
}
129+
130+
const failureMessage = getAnimationsFailure(linesCount, this.animationsLinesLimit);
131+
132+
for (const animation of metadata.animations) {
133+
this.addFailureAtNode(animation.node!, failureMessage);
134+
}
135+
}
136+
103137
private getInlineStylesLinesCount(metadata: ComponentMetadata): number {
104138
return (metadata.styles || []).reduce((previousValue, currentValue) => {
105139
if (!currentValue.url) {
@@ -123,6 +157,7 @@ export class MaxInlineDeclarationsWalker extends NgWalker {
123157
this.addFailureAtNode(style.node!, failureMessage);
124158
}
125159
}
160+
126161
private getTemplateLinesCount(metadata: ComponentMetadata): number {
127162
return this.hasInlineTemplate(metadata) ? this.getLinesCount(metadata.template.template.source) : 0;
128163
}

src/util/ngQuery.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import * as ts from 'typescript';
22
import { decoratorArgument, getInitializer, getStringInitializerFromProperty, isProperty, WithStringInitializer } from './astQuery';
33
import { Maybe } from './function';
44

5+
export function getAnimations(dec: ts.Decorator): Maybe<ts.ArrayLiteralExpression | undefined> {
6+
return decoratorArgument(dec).bind(expr => {
7+
const property = expr!.properties.find(p => isProperty('animations', p))!;
8+
9+
return getInitializer(property).fmap(expr => (ts.isArrayLiteralExpression(expr!) ? (expr as ts.ArrayLiteralExpression) : undefined));
10+
});
11+
}
12+
513
export function getInlineStyle(dec: ts.Decorator): Maybe<ts.ArrayLiteralExpression | undefined> {
614
return decoratorArgument(dec).bind(expr => {
715
const property = expr!.properties.find(p => isProperty('styles', p))!;

0 commit comments

Comments
 (0)