Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Windows line endings correctly in inline templates #247

Merged
merged 3 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions packages/transform/__tests__/debug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,136 @@ describe('Debug utilities', () => {
|"
`);
});

test('Windows line endings', () => {
let script = {
filename: 'test.ts',
contents: stripIndent`
import Component, { hbs } from '@glint/environment-glimmerx/component';

export default class MyComponent extends Component {
private bar = 'hi';

static template = hbs\`
<HelperComponent @foo={{this.bar}} />
\`;
}

class HelperComponent extends Component<{ Args: { foo: string } }> {
static template = hbs\`
<p ...attributes>
Hello, {{@foo}}!

{{! @glint-expect-error: no @bar arg }}
{{@bar}}
</p>
\`;
}
`.replace(/\n/g, '\r\n'),
};

let transformedModule = rewriteModule({ script }, GlintEnvironment.load('glimmerx'));

expect(transformedModule?.toDebugString()).toMatchInlineSnapshot(`
"TransformedModule

| Mapping: Template
| hbs(174:226): hbs\`\\\\r\\\\n <HelperComponent @foo={{this.bar}} />\\\\r\\\\n \`
| ts(174:533): ({} as typeof import(\\"@glint/environment-glimmerx/-private/dsl\\")).template(function(𝚪: import(\\"@glint/environment-glimmerx/-private/dsl\\").ResolveContext<MyComponent>, χ: typeof import(\\"@glint/environment-glimmerx/-private/dsl\\")) {\\\\n hbs;\\\\n {\\\\n const 𝛄 = χ.emitComponent(χ.resolve(HelperComponent)({ foo: 𝚪.this.bar }));\\\\n 𝛄;\\\\n }\\\\n 𝚪; χ;\\\\n}) as unknown
|
| | Mapping: Identifier
| | hbs(174:174):
| | ts(328:339): MyComponent
| |
| | Mapping: ElementNode
| | hbs(184:221): <HelperComponent @foo={{this.bar}} />
| | ts(413:511): {\\\\n const 𝛄 = χ.emitComponent(χ.resolve(HelperComponent)({ foo: 𝚪.this.bar }));\\\\n 𝛄;\\\\n }
| |
| | | Mapping: Identifier
| | | hbs(185:200): HelperComponent
| | | ts(458:473): HelperComponent
| | |
| | | Mapping: AttrNode
| | | hbs(201:218): @foo={{this.bar}}
| | | ts(477:493): foo: 𝚪.this.bar
| | |
| | | | Mapping: Identifier
| | | | hbs(202:205): foo
| | | | ts(477:480): foo
| | | |
| | | | Mapping: MustacheStatement
| | | | hbs(206:218): {{this.bar}}
| | | | ts(482:493): 𝚪.this.bar
| | | |
| | | | | Mapping: PathExpression
| | | | | hbs(208:216): this.bar
| | | | | ts(482:493): 𝚪.this.bar
| | | | |
| | | | | | Mapping: Identifier
| | | | | | hbs(208:212): this
| | | | | | ts(485:489): this
| | | | | |
| | | | | | Mapping: Identifier
| | | | | | hbs(213:216): bar
| | | | | | ts(490:493): bar
| | | | | |
| | | | |
| | | |
| | |
| |
|

| Mapping: Template
| hbs(324:455): hbs\`\\\\r\\\\n <p ...attributes>\\\\r\\\\n Hello, {{@foo}}!\\\\r\\\\n\\\\r\\\\n {{! @glint-expect-error: no @bar arg }}\\\\r\\\\n {{@bar}}\\\\r\\\\n </p>\\\\r\\\\n \`
| ts(631:1095): ({} as typeof import(\\"@glint/environment-glimmerx/-private/dsl\\")).template(function(𝚪: import(\\"@glint/environment-glimmerx/-private/dsl\\").ResolveContext<HelperComponent>, χ: typeof import(\\"@glint/environment-glimmerx/-private/dsl\\")) {\\\\n hbs;\\\\n {\\\\n const 𝛄 = χ.emitElement(\\"p\\");\\\\n χ.applySplattributes(𝚪.element, 𝛄.element);\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.foo)({}));\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.bar)({}));\\\\n }\\\\n 𝚪; χ;\\\\n}) as unknown
|
| | Mapping: Identifier
| | hbs(324:324):
| | ts(785:800): HelperComponent
| |
| | Mapping: ElementNode
| | hbs(334:450): <p ...attributes>\\\\r\\\\n Hello, {{@foo}}!\\\\r\\\\n\\\\r\\\\n {{! @glint-expect-error: no @bar arg }}\\\\r\\\\n {{@bar}}\\\\r\\\\n </p>
| | ts(874:1073): {\\\\n const 𝛄 = χ.emitElement(\\"p\\");\\\\n χ.applySplattributes(𝚪.element, 𝛄.element);\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.foo)({}));\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.bar)({}));\\\\n }
| |
| | | Mapping: AttrNode
| | | hbs(337:350): ...attributes
| | | ts(913:962): χ.applySplattributes(𝚪.element, 𝛄.element);
| | |
| | | Mapping: MustacheStatement
| | | hbs(366:374): {{@foo}}
| | | ts(963:1014): χ.emitValue(χ.resolveOrReturn(𝚪.args.foo)({}))
| | |
| | | | Mapping: PathExpression
| | | | hbs(368:372): @foo
| | | | ts(997:1008): 𝚪.args.foo
| | | |
| | | | | Mapping: Identifier
| | | | | hbs(369:372): foo
| | | | | ts(1005:1008):foo
| | | | |
| | | |
| | |
| | | Mapping: MustacheCommentStatement
| | | hbs(385:424): {{! @glint-expect-error: no @bar arg }}
| | | ts(1016:1016):
| | |
| | | Mapping: MustacheStatement
| | | hbs(432:440): {{@bar}}
| | | ts(1016:1067):χ.emitValue(χ.resolveOrReturn(𝚪.args.bar)({}))
| | |
| | | | Mapping: PathExpression
| | | | hbs(434:438): @bar
| | | | ts(1050:1061):𝚪.args.bar
| | | |
| | | | | Mapping: Identifier
| | | | | hbs(435:438): bar
| | | | | ts(1058:1061):bar
| | | | |
| | | |
| | |
| |
|"
`);
});
});
});
19 changes: 19 additions & 0 deletions packages/transform/__tests__/offset-mapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ describe('Source-to-source offset mapping', () => {
let module = rewriteCompanionTemplate({ backing, contents: '{{@foo}}' });
expectTokenMapping(module, 'foo');
});

test('Windows line endings', () => {
let module = rewriteCompanionTemplate({
backing: 'none',
contents: `Hello, <World />!\r\n\r\n{{this.foo}}\r\n`,
});

expectTokenMapping(module, 'World');
expectTokenMapping(module, 'foo');
});
});

describe('inline template', () => {
Expand Down Expand Up @@ -378,6 +388,15 @@ describe('Source-to-source offset mapping', () => {
expectTokenMapping(module, 'name', { occurrence: 1 });
});
});

test('Windows line endings', () => {
let module = rewriteInlineTemplate({
contents: `Hello, <World />!\r\n\r\n{{this.foo}}\r\n`,
});

expectTokenMapping(module, 'World');
expectTokenMapping(module, 'foo');
});
});

describe('spans outside of mapped segments', () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/transform/src/inlining/tagged-strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,16 @@ export function calculateTaggedTemplateSpans(
assert(path.node.start, 'Missing location info');
assert(path.node.end, 'Missing location info');

let contentStart = quasis[0].start;
let contentEnd = quasis[0].end;
assert(contentStart && contentEnd, 'Missing location info');

// Access the contents directly from source rather than the AST node, as
// template literals' line endings are subject to normalization during parse.
let contents = script.contents.slice(contentStart, contentEnd);

// Pad the template to account for the tag and surrounding ` characters
let template = `${''.padStart(tagName.length)} ${quasis[0].value.raw} `;
let template = `${''.padStart(tagName.length)} ${contents} `;

// Emit a use of the template tag so it's not considered unused
let preamble = [`${tagName};`];
Expand Down
20 changes: 12 additions & 8 deletions packages/transform/src/mapping-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ export default class MappingTree {
lines.push(`${indent}Mapping: ${sourceNode.type}`);

lines.push(
`${indent}${` hbs(${hbsStart}:${hbsEnd}):`.padEnd(15)}${originalSource
.slice(originalRange.start, originalRange.end)
.trim()
.replace(/\n/g, '\\n')}`
`${indent}${` hbs(${hbsStart}:${hbsEnd}):`.padEnd(15)}${this.getSourceRange(
originalSource,
originalRange
)}`
);

lines.push(
`${indent}${` ts(${tsStart}:${tsEnd}):`.padEnd(15)}${transformedSource
.slice(transformedRange.start, transformedRange.end)
.trim()
.replace(/\n/g, '\\n')}`
`${indent}${` ts(${tsStart}:${tsEnd}):`.padEnd(15)}${this.getSourceRange(
transformedSource,
transformedRange
)}`
);

lines.push(indent);
Expand All @@ -117,4 +117,8 @@ export default class MappingTree {

return lines.map((line) => line.trimEnd()).join('\n');
}

private getSourceRange(source: string, range: Range): string {
return source.slice(range.start, range.end).trim().replace(/\n/g, '\\n').replace(/\r/g, '\\r');
}
}