Skip to content

Commit f12f27b

Browse files
wKozamgechev
authored andcommitted
feat(rule): add new Rule RelativePathExternalResourcesRule (#725)
* feat(rule): add new Rule RelativePathExternalResourcesRule * feat(rule): fix regexp * feat(rule): fix message
1 parent 465d5f8 commit f12f27b

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export { Rule as UseOutputPropertyDecoratorRule } from './useOutputPropertyDecor
3838
export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule';
3939
export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule';
4040
export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule';
41+
export { Rule as RelativePathExternalResourcesRule } from './relativeUrlPrefixRule';
4142

4243
export * from './angular';
4344

src/relativeUrlPrefixRule.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { IOptions, IRuleMetadata, RuleFailure, Rules } from 'tslint/lib';
2+
import { SourceFile } from 'typescript/lib/typescript';
3+
import { NgWalker } from './angular/ngWalker';
4+
import * as ts from 'typescript';
5+
6+
export class Rule extends Rules.AbstractRule {
7+
static readonly metadata: IRuleMetadata = {
8+
description: "The ./ prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix.",
9+
descriptionDetails: 'See more at https://angular.io/styleguide#style-05-04.',
10+
rationale: 'A component relative URL requires no change when you move the component files, as long as the files stay together.',
11+
ruleName: 'relative-url-prefix',
12+
options: null,
13+
optionsDescription: 'Not configurable.',
14+
type: 'maintainability',
15+
typescriptOnly: true
16+
};
17+
18+
static readonly FAILURE_STRING = 'The ./ prefix is standard syntax for relative URLs. (https://angular.io/styleguide#style-05-04)';
19+
20+
apply(sourceFile: SourceFile): RuleFailure[] {
21+
return this.applyWithWalker(new RelativePathExternalResourcesRuleWalker(sourceFile, this.getOptions()));
22+
}
23+
}
24+
25+
export class RelativePathExternalResourcesRuleWalker extends NgWalker {
26+
constructor(sourceFile: SourceFile, options: IOptions) {
27+
super(sourceFile, options);
28+
}
29+
30+
visitClassDecorator(decorator: ts.Decorator) {
31+
if (ts.isCallExpression(decorator.expression) && decorator.expression.arguments) {
32+
decorator.expression.arguments.forEach(arg => {
33+
if (ts.isObjectLiteralExpression(arg) && arg.properties) {
34+
arg.properties.forEach((prop: any) => {
35+
if (prop && prop.name.text === 'templateUrl') {
36+
const url = prop.initializer.text;
37+
this.checkTemplateUrl(url, prop.initializer);
38+
} else if (prop && prop.name.text === 'styleUrls') {
39+
if (prop.initializer.elements.length > 0) {
40+
prop.initializer.elements.forEach(e => {
41+
const url = e.text;
42+
this.checkStyleUrls(e);
43+
});
44+
}
45+
}
46+
});
47+
}
48+
});
49+
}
50+
super.visitClassDecorator(decorator);
51+
}
52+
53+
private checkTemplateUrl(url: string, initializer: ts.StringLiteral) {
54+
if (url && !/^\.\/[^\.\/|\.\.\/]/.test(url)) {
55+
this.addFailureAtNode(initializer, Rule.FAILURE_STRING);
56+
}
57+
}
58+
59+
private checkStyleUrls(token: ts.StringLiteral) {
60+
if (token && token.text) {
61+
if (!/^\.\/[^\.\/|\.\.\/]/.test(token.text)) {
62+
this.addFailureAtNode(token, Rule.FAILURE_STRING);
63+
}
64+
}
65+
}
66+
}

test/relativeUrlPrefixRule.spec.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { Rule } from '../src/relativeUrlPrefixRule';
2+
import { assertAnnotated, assertSuccess } from './testHelper';
3+
4+
const {
5+
metadata: { ruleName }
6+
} = Rule;
7+
8+
describe(ruleName, () => {
9+
describe('styleUrls', () => {
10+
describe('success', () => {
11+
it('should succeed when a relative URL is prefixed by ./', () => {
12+
const source = `
13+
@Component({
14+
styleUrls: ['./foobar.css']
15+
})
16+
class Test {}
17+
`;
18+
assertSuccess(ruleName, source);
19+
});
20+
21+
it('should succeed when all relative URLs is prefixed by ./', () => {
22+
const source = `
23+
@Component({
24+
styleUrls: ['./foo.css', './bar.css', './whatyouwant.css']
25+
})
26+
class Test {}
27+
`;
28+
assertSuccess(ruleName, source);
29+
});
30+
});
31+
32+
describe('failure', () => {
33+
it("should fail when a relative URL isn't prefixed by ./", () => {
34+
const source = `
35+
@Component({
36+
styleUrls: ['foobar.css']
37+
~~~~~~~~~~~~
38+
})
39+
class Test {}
40+
`;
41+
assertAnnotated({
42+
ruleName,
43+
message: Rule.FAILURE_STRING,
44+
source
45+
});
46+
});
47+
48+
it("should fail when a relative URL isn't prefixed by ./", () => {
49+
const source = `
50+
@Component({
51+
styleUrls: ['./../foobar.css']
52+
~~~~~~~~~~~~~~~~~
53+
})
54+
class Test {}
55+
`;
56+
assertAnnotated({
57+
ruleName,
58+
message: Rule.FAILURE_STRING,
59+
source
60+
});
61+
});
62+
63+
it("should fail when one relative URLs isn't prefixed by ./", () => {
64+
const source = `
65+
@Component({
66+
styleUrls: ['./foo.css', 'bar.css', './whatyouwant.css']
67+
~~~~~~~~~
68+
})
69+
class Test {}
70+
`;
71+
assertAnnotated({
72+
ruleName,
73+
message: Rule.FAILURE_STRING,
74+
source
75+
});
76+
});
77+
});
78+
});
79+
80+
describe('templateUrl', () => {
81+
describe('success', () => {
82+
it('should succeed when a relative URL is prefixed by ./', () => {
83+
const source = `
84+
@Component({
85+
templateUrl: './foobar.html'
86+
})
87+
class Test {}
88+
`;
89+
assertSuccess(ruleName, source);
90+
});
91+
});
92+
93+
describe('failure', () => {
94+
it("should succeed when a relative URL isn't prefixed by ./", () => {
95+
const source = `
96+
@Component({
97+
templateUrl: 'foobar.html'
98+
~~~~~~~~~~~~~
99+
})
100+
class Test {}
101+
`;
102+
assertAnnotated({
103+
ruleName,
104+
message: Rule.FAILURE_STRING,
105+
source
106+
});
107+
});
108+
109+
it('should fail when a relative URL is prefixed by ../', () => {
110+
const source = `
111+
@Component({
112+
templateUrl: '../foobar.html'
113+
~~~~~~~~~~~~~~~~
114+
})
115+
class Test {}
116+
`;
117+
assertAnnotated({
118+
ruleName,
119+
message: Rule.FAILURE_STRING,
120+
source
121+
});
122+
});
123+
124+
it('should fail when a relative URL is prefixed by ../', () => {
125+
const source = `
126+
@Component({
127+
templateUrl: '.././foobar.html'
128+
~~~~~~~~~~~~~~~~~~
129+
})
130+
class Test {}
131+
`;
132+
assertAnnotated({
133+
ruleName,
134+
message: Rule.FAILURE_STRING,
135+
source
136+
});
137+
});
138+
});
139+
});
140+
});

0 commit comments

Comments
 (0)