Skip to content

Commit b30cf6e

Browse files
devversionjelbourn
authored andcommitted
refactor(schematics): gracefully handle non statically analyzable values (#13143)
Currently we just treat every value of `@Component#template` or `@Component#styles` as `ts.StringLiteral`. This works in the most cases, but there can be also developers that inline their template using some specific Webpack loaders or variables.
1 parent 84ae42c commit b30cf6e

12 files changed

+87
-54
lines changed

src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStylesheetRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class Walker extends ComponentWalker {
4444
this._reportExtraStylesheetFiles();
4545
}
4646

47-
visitInlineStylesheet(literal: ts.StringLiteral) {
47+
visitInlineStylesheet(literal: ts.StringLiteralLike) {
4848
this._createReplacementsForContent(literal, literal.getText())
4949
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
5050
}

src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsTemplateRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class Walker extends ComponentWalker {
3434
/** Change data that upgrades to the specified target version. */
3535
data = getChangesForTarget(this.getOptions()[0], attributeSelectors);
3636

37-
visitInlineTemplate(template: ts.StringLiteral) {
37+
visitInlineTemplate(template: ts.StringLiteralLike) {
3838
this._createReplacementsForContent(template, template.getText())
3939
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4040
}

src/lib/schematics/update/rules/checkTemplateMiscRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class Rule extends Rules.AbstractRule {
2626

2727
export class Walker extends ComponentWalker {
2828

29-
visitInlineTemplate(template: ts.StringLiteral) {
29+
visitInlineTemplate(template: ts.StringLiteralLike) {
3030
this._createFailuresForContent(template, template.getText())
3131
.forEach(data => this.addFailureFromStartToEnd(data.start, data.end, data.message));
3232
}

src/lib/schematics/update/rules/css-selectors/cssSelectorsStylesheetRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class Walker extends ComponentWalker {
4343
this._reportExtraStylesheetFiles();
4444
}
4545

46-
visitInlineStylesheet(literal: ts.StringLiteral) {
46+
visitInlineStylesheet(literal: ts.StringLiteralLike) {
4747
this._createReplacementsForContent(literal, literal.getText())
4848
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4949
}

src/lib/schematics/update/rules/css-selectors/cssSelectorsTemplateRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class Walker extends ComponentWalker {
3434
/** Change data that upgrades to the specified target version. */
3535
data = getChangesForTarget(this.getOptions()[0], cssSelectors);
3636

37-
visitInlineTemplate(template: ts.StringLiteral) {
37+
visitInlineTemplate(template: ts.StringLiteralLike) {
3838
this._createReplacementsForContent(template, template.getText())
3939
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4040
}

src/lib/schematics/update/rules/element-selectors/elementSelectorsStylesheetRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class Walker extends ComponentWalker {
4343
this._reportExtraStylesheetFiles();
4444
}
4545

46-
visitInlineStylesheet(literal: ts.StringLiteral) {
46+
visitInlineStylesheet(literal: ts.StringLiteralLike) {
4747
this._createReplacementsForContent(literal, literal.getText())
4848
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4949
}

src/lib/schematics/update/rules/element-selectors/elementSelectorsTemplateRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class Walker extends ComponentWalker {
3434
/** Change data that upgrades to the specified target version. */
3535
data = getChangesForTarget(this.getOptions()[0], elementSelectors);
3636

37-
visitInlineTemplate(template: ts.StringLiteral) {
37+
visitInlineTemplate(template: ts.StringLiteralLike) {
3838
this._createReplacementsForContent(template, template.getText())
3939
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4040
}

src/lib/schematics/update/rules/input-names/inputNamesStylesheetRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class Walker extends ComponentWalker {
4848
this._reportExtraStylesheetFiles();
4949
}
5050

51-
visitInlineStylesheet(literal: ts.StringLiteral) {
51+
visitInlineStylesheet(literal: ts.StringLiteralLike) {
5252
this._createReplacementsForContent(literal, literal.getText())
5353
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
5454
}

src/lib/schematics/update/rules/input-names/inputNamesTemplateRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class Walker extends ComponentWalker {
3434
/** Change data that upgrades to the specified target version. */
3535
data = getChangesForTarget(this.getOptions()[0], inputNames);
3636

37-
visitInlineTemplate(template: ts.StringLiteral) {
37+
visitInlineTemplate(template: ts.StringLiteralLike) {
3838
this._createReplacementsForContent(template, template.getText())
3939
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4040
}

src/lib/schematics/update/rules/output-names/outputNamesTemplateRule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class Walker extends ComponentWalker {
3434
/** Change data that upgrades to the specified target version. */
3535
data = getChangesForTarget(this.getOptions()[0], outputNames);
3636

37-
visitInlineTemplate(template: ts.StringLiteral) {
37+
visitInlineTemplate(template: ts.StringLiteralLike) {
3838
this._createReplacementsForContent(template, template.getText())
3939
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
4040
}

src/lib/schematics/update/tslint/component-walker.spec.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ describe('ComponentWalker', () => {
5252
const sourceFile = createSourceFile(externalStylesheetSource);
5353
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
5454

55-
spyOn(console, 'error');
56-
5755
expect(() => walker.walk(sourceFile)).not.toThrow();
58-
expect(console.error).toHaveBeenCalledTimes(1);
56+
expect(walker.getFailures().length).toBe(1);
5957
});
6058

6159
it('should report inline templates', () => {
@@ -90,10 +88,35 @@ describe('ComponentWalker', () => {
9088
const sourceFile = createSourceFile(externalTemplateSource);
9189
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
9290

93-
spyOn(console, 'error');
91+
expect(() => walker.walk(sourceFile)).not.toThrow();
92+
expect(walker.getFailures().length).toBe(1);
93+
});
94+
95+
it('should not throw if the inline template could not be resolved', () => {
96+
const sourceFile = createSourceFile(`
97+
@Component({
98+
template: myTemplate,
99+
})
100+
export class MyComponent {}
101+
`);
102+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
103+
104+
expect(() => walker.walk(sourceFile)).not.toThrow();
105+
});
106+
107+
it('should not throw if inline styles could not be resolved', () => {
108+
const sourceFile = createSourceFile(`
109+
@Component({
110+
styles: [styleA, styleB, 'c', 'd'],
111+
})
112+
export class MyComponent {}
113+
`);
114+
const walker = new ComponentWalker(sourceFile, defaultRuleOptions);
115+
116+
spyOn(walker, 'visitInlineStylesheet');
94117

95118
expect(() => walker.walk(sourceFile)).not.toThrow();
96-
expect(console.error).toHaveBeenCalledTimes(1);
119+
expect(walker.visitInlineStylesheet).toHaveBeenCalledTimes(2);
97120
});
98121
});
99122

src/lib/schematics/update/tslint/component-walker.ts

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import {createComponentFile, ExternalResource} from './component-file';
2121
*/
2222
export class ComponentWalker extends RuleWalker {
2323

24-
visitInlineTemplate(_template: ts.StringLiteral) {}
25-
visitInlineStylesheet(_stylesheet: ts.StringLiteral) {}
24+
visitInlineTemplate(_template: ts.StringLiteralLike) {}
25+
visitInlineStylesheet(_stylesheet: ts.StringLiteralLike) {}
2626

2727
visitExternalTemplate(_template: ExternalResource) {}
2828
visitExternalStylesheet(_stylesheet: ExternalResource) {}
@@ -63,45 +63,26 @@ export class ComponentWalker extends RuleWalker {
6363

6464
for (const property of directiveMetadata.properties as ts.NodeArray<ts.PropertyAssignment>) {
6565
const propertyName = property.name.getText();
66-
const initializerKind = property.initializer.kind;
6766

68-
if (propertyName === 'template') {
69-
this.visitInlineTemplate(property.initializer as ts.StringLiteral);
67+
if (propertyName === 'template' && ts.isStringLiteralLike(property.initializer)) {
68+
this.visitInlineTemplate(property.initializer);
7069
}
7170

72-
if (propertyName === 'templateUrl' && initializerKind === ts.SyntaxKind.StringLiteral) {
73-
this._reportExternalTemplate(property.initializer as ts.StringLiteral);
71+
if (propertyName === 'templateUrl' && ts.isStringLiteralLike(property.initializer)) {
72+
this._reportExternalTemplate(property.initializer);
7473
}
7574

76-
if (propertyName === 'styles' && initializerKind === ts.SyntaxKind.ArrayLiteralExpression) {
77-
this._reportInlineStyles(property.initializer as ts.ArrayLiteralExpression);
75+
if (propertyName === 'styles' && ts.isArrayLiteralExpression(property.initializer)) {
76+
this._reportInlineStyles(property.initializer);
7877
}
7978

80-
if (propertyName === 'styleUrls' &&
81-
initializerKind === ts.SyntaxKind.ArrayLiteralExpression) {
82-
this._visitExternalStylesArrayLiteral(property.initializer as ts.ArrayLiteralExpression);
79+
if (propertyName === 'styleUrls' && ts.isArrayLiteralExpression(property.initializer)) {
80+
this._visitExternalStylesArrayLiteral(property.initializer);
8381
}
8482
}
8583
}
8684

87-
private _reportInlineStyles(inlineStyles: ts.ArrayLiteralExpression) {
88-
inlineStyles.elements.forEach(element => {
89-
this.visitInlineStylesheet(element as ts.StringLiteral);
90-
});
91-
}
92-
93-
private _visitExternalStylesArrayLiteral(styleUrls: ts.ArrayLiteralExpression) {
94-
styleUrls.elements.forEach((node: ts.StringLiteral) => {
95-
const stylePath = resolve(join(dirname(this.getSourceFile().fileName), node.text));
96-
97-
// Do not report the specified additional files multiple times.
98-
if (!this.extraFiles.has(stylePath)) {
99-
this._reportExternalStyle(stylePath);
100-
}
101-
});
102-
}
103-
104-
private _reportExternalTemplate(node: ts.StringLiteral) {
85+
private _reportExternalTemplate(node: ts.StringLiteralLike) {
10586
const templatePath = resolve(join(dirname(this.getSourceFile().fileName), node.text));
10687

10788
// Do not report the specified additional files multiple times.
@@ -111,8 +92,7 @@ export class ComponentWalker extends RuleWalker {
11192

11293
// Check if the external template file exists before proceeding.
11394
if (!existsSync(templatePath)) {
114-
console.error(`PARSE ERROR: ${this.getSourceFile().fileName}:` +
115-
` Could not find template: "${templatePath}".`);
95+
this._createResourceNotFoundFailure(node, templatePath);
11696
return;
11797
}
11898

@@ -122,14 +102,35 @@ export class ComponentWalker extends RuleWalker {
122102
this.visitExternalTemplate(templateFile);
123103
}
124104

125-
_reportExternalStyle(stylePath: string) {
126-
// Check if the external stylesheet file exists before proceeding.
127-
if (!existsSync(stylePath)) {
128-
console.error(`PARSE ERROR: ${this.getSourceFile().fileName}: ` +
129-
`Could not find stylesheet: "${stylePath}".`);
130-
return;
131-
}
105+
private _reportInlineStyles(expression: ts.ArrayLiteralExpression) {
106+
expression.elements.forEach(node => {
107+
if (ts.isStringLiteralLike(node)) {
108+
this.visitInlineStylesheet(node);
109+
}
110+
});
111+
}
112+
113+
private _visitExternalStylesArrayLiteral(expression: ts.ArrayLiteralExpression) {
114+
expression.elements.forEach(node => {
115+
if (ts.isStringLiteralLike(node)) {
116+
const stylePath = resolve(join(dirname(this.getSourceFile().fileName), node.text));
117+
118+
// Do not report the specified additional files multiple times.
119+
if (this.extraFiles.has(stylePath)) {
120+
return;
121+
}
122+
123+
// Check if the external stylesheet file exists before proceeding.
124+
if (!existsSync(stylePath)) {
125+
return this._createResourceNotFoundFailure(node, stylePath);
126+
}
127+
128+
this._reportExternalStyle(stylePath);
129+
}
130+
});
131+
}
132132

133+
_reportExternalStyle(stylePath: string) {
133134
// Create a fake TypeScript source file that includes the stylesheet content.
134135
const stylesheetFile = createComponentFile(stylePath, readFileSync(stylePath, 'utf8'));
135136

@@ -159,4 +160,13 @@ export class ComponentWalker extends RuleWalker {
159160

160161
return null;
161162
}
163+
164+
/**
165+
* Creates a TSLint failure that reports that the resource file that belongs to the specified
166+
* TypeScript node could not be resolved in the file system.
167+
*/
168+
private _createResourceNotFoundFailure(node: ts.Node, resourceUrl: string) {
169+
this.addFailureAtNode(node, `Could not resolve resource file: "${resourceUrl}". ` +
170+
`Skipping automatic upgrade for this file.`);
171+
}
162172
}

0 commit comments

Comments
 (0)