Skip to content

Commit

Permalink
fix(eslint-plugin-template): support escape chars in inline templates (
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry committed Sep 20, 2021
1 parent 8800bdf commit 8b89ec7
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 19 deletions.
40 changes: 25 additions & 15 deletions packages/eslint-plugin-template/src/processors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import ts from 'typescript';

function quickGetRangeForTemplate(text: string, template: string) {
text = text.replace(/\r\n/g, '\n');
const start = text.indexOf(template);
return [start, start + template.length];
}

const rangeMap = new Map();

/**
Expand Down Expand Up @@ -137,24 +131,40 @@ export function preprocessComponentFile(
continue;
}

if (
!ts.isPropertyAssignment(templateProperty) ||
!ts.isStringLiteralLike(templateProperty.initializer)
) {
if (!ts.isPropertyAssignment(templateProperty)) {
continue;
}

const templateText = templateProperty.initializer.text;
let templateText: string | undefined;

const range = quickGetRangeForTemplate(text, templateText);
const templatePropertyInitializer = templateProperty.initializer;
if (ts.isNoSubstitutionTemplateLiteral(templatePropertyInitializer)) {
templateText = templatePropertyInitializer.rawText;
}

if (ts.isTemplateExpression(templatePropertyInitializer)) {
templateText = templatePropertyInitializer.getText();
}

if (ts.isStringLiteral(templatePropertyInitializer)) {
templateText = templatePropertyInitializer.text;
}

// The template initializer is somehow not a string literal or a string template
if (!templateText) {
continue;
}

const inlineTemplateTmpFilename = `inline-template-${++id}.component.html`;

const start = templateProperty.initializer.getStart();
const end = templateProperty.initializer.getEnd();

rangeMap.set(inlineTemplateTmpFilename, {
range,
range: [start, end],
lineAndCharacter: {
start: sourceFile.getLineAndCharacterOfPosition(range[0]),
end: sourceFile.getLineAndCharacterOfPosition(range[1]),
start: sourceFile.getLineAndCharacterOfPosition(start),
end: sourceFile.getLineAndCharacterOfPosition(end),
},
});

Expand Down
95 changes: 91 additions & 4 deletions packages/eslint-plugin-template/tests/processors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,32 @@ describe('extract-inline-html', () => {
});
});

describe('components with simple inline template string', () => {
it('should work when the template initializer is a simple string literal', () => {
const input = `
@Component({
selector: 'app-a',
template: '<h1>Hello, A!</h1>',
styleUrls: ['./a.component.scss']
})
export class ComponentA {}
`;

expect(
processors['extract-inline-html'].preprocess(
input,
'test.component.ts',
),
).toEqual([
input,
{
filename: 'inline-template-1.component.html',
text: '<h1>Hello, A!</h1>',
},
]);
});
});

describe('components with inline templates', () => {
const inlineTemplate = `
<input type="text" name="foo" ([ngModel])="foo">
Expand Down Expand Up @@ -295,16 +321,77 @@ describe('extract-inline-html', () => {
tc.input,
{
filename: 'inline-template-1.component.html',
text: inlineTemplate.replace(/\r\n/g, '\n'),
text: inlineTemplate,
},
]);
});
});
});

// https://github.com/angular-eslint/angular-eslint/issues/207
describe('components with inline templates containing escape characters', () => {
/* eslint-disable no-useless-escape */
const inlineTemplateContainingEscapeCharacters1 = `
<input [value]="\\\${{this.aCost}} USD"></input>
<input [value]="'It\\\'s free'"></input>
`;
/* eslint-enable no-useless-escape */

const testCases = [
// NOTE: intentionally using variable for input and expected extraction assertion
{
input: `
import {Component} from '@angular/core';
@Component({
selector: 'app-root',
template: \`${inlineTemplateContainingEscapeCharacters1}\`,
styleUrls: ['./app.component.scss']
})
export class AppComponent {
public aCost = 20;
}
`,
expectedExtraction: inlineTemplateContainingEscapeCharacters1,
},
// NOTE: intentionally NOT using a variable within the test code to ensure broader coverage (and to avoid test-specific escape logic)
{
input: `
@Component({
selector: 'app-component',
template: \`
<component
[prop]="true ? 'test for \\'@\\' escaping' : 'false'"
></component>
\`
})
export class AppComponent {}
`,
expectedExtraction: `
<component
[prop]="true ? 'test for \\'@\\' escaping' : 'false'"
></component>
`,
},
];

testCases.forEach((tc, i) => {
it(`should extract the inline HTML of components with inline templates, CASE: ${i}`, () => {
expect(
processors['extract-inline-html'].preprocess(
tc.input,
'test.component.ts',
),
).toEqual([
tc.input,
{
filename: 'inline-template-1.component.html',
text: tc.expectedExtraction,
},
]);
});
});
});

/**
* Currently explicitly unsupported...
*/
describe('multiple components in a single file', () => {
it(`should support extracting inline templates from multiple Components in a single file`, () => {
const input = `
Expand Down

0 comments on commit 8b89ec7

Please sign in to comment.