Skip to content

Commit 3785f99

Browse files
committed
Add .gts support
ember-cli added support for .gts files. This now matches the setup used there.
1 parent 8bede4c commit 3785f99

File tree

5 files changed

+166
-11
lines changed

5 files changed

+166
-11
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
},
2929
"dependencies": {
3030
"chalk": "^4.0.0",
31+
"content-tag": "^2.0.2",
3132
"remove-types": "^1.0.0"
3233
},
3334
"devDependencies": {

src/typescript-blueprint-polyfill.js

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
const { removeTypes } = require('remove-types');
21
const chalk = require('chalk');
3-
const { replaceExtension, isTypeScriptFile } = require('./utils');
2+
const path = require('path');
3+
const {
4+
replaceExtension,
5+
replaceTypeScriptExtension,
6+
isTypeScriptFile,
7+
} = require('./utils');
48

59
module.exports = function (context) {
610
const blueprintClass = context._super.constructor.prototype;
@@ -73,12 +77,12 @@ module.exports = function (context) {
7377
context.convertToJS = async function (fileInfo) {
7478
let rendered = await fileInfo.render();
7579

76-
const transformed = await removeTypes(rendered);
77-
78-
fileInfo.rendered = transformed;
79-
80-
fileInfo.displayPath = replaceExtension(fileInfo.displayPath, '.js');
81-
fileInfo.outputPath = replaceExtension(fileInfo.outputPath, '.js');
80+
fileInfo.rendered = await removeTypes(
81+
path.extname(fileInfo.displayPath),
82+
rendered
83+
);
84+
fileInfo.displayPath = replaceTypeScriptExtension(fileInfo.displayPath);
85+
fileInfo.outputPath = replaceTypeScriptExtension(fileInfo.outputPath);
8286

8387
return fileInfo;
8488
};
@@ -147,3 +151,81 @@ module.exports = function (context) {
147151
}, []);
148152
};
149153
};
154+
155+
/**
156+
Removes types from .ts and .gts files.
157+
Based on the code in ember-cli: https://github.com/ember-cli/ember-cli/blob/2dc099a90dc0a4e583e43e4020d691b342f1e891/lib/models/blueprint.js#L531
158+
159+
@private
160+
@method removeTypes
161+
@param {string} extension
162+
@param {string} code
163+
@return {Promise}
164+
*/
165+
async function removeTypes(extension, code) {
166+
const { removeTypes: removeTypesFn } = require('remove-types');
167+
168+
if (extension === '.gts') {
169+
const { Preprocessor } = require('content-tag');
170+
const preprocessor = new Preprocessor();
171+
// Strip template tags
172+
const templateTagIdentifier = (index) =>
173+
`template = __TEMPLATE_TAG_${index}__;`;
174+
const templateTagIdentifierBraces = (index) =>
175+
`(template = __TEMPLATE_TAG_${index}__);`;
176+
const templateTagMatches = preprocessor.parse(code);
177+
let strippedCode = code;
178+
for (let i = 0; i < templateTagMatches.length; i++) {
179+
const match = templateTagMatches[i];
180+
const templateTag = substringBytes(
181+
code,
182+
match.range.start,
183+
match.range.end
184+
);
185+
strippedCode = strippedCode.replace(
186+
templateTag,
187+
templateTagIdentifier(i)
188+
);
189+
}
190+
191+
// Remove types
192+
const transformed = await removeTypesFn(strippedCode);
193+
194+
// Readd stripped template tags
195+
let transformedWithTemplateTag = transformed;
196+
for (let i = 0; i < templateTagMatches.length; i++) {
197+
const match = templateTagMatches[i];
198+
const templateTag = substringBytes(
199+
code,
200+
match.range.start,
201+
match.range.end
202+
);
203+
transformedWithTemplateTag = transformedWithTemplateTag.replace(
204+
templateTagIdentifier(i),
205+
templateTag
206+
);
207+
transformedWithTemplateTag = transformedWithTemplateTag.replace(
208+
templateTagIdentifierBraces(i),
209+
templateTag
210+
);
211+
}
212+
213+
return transformedWithTemplateTag;
214+
}
215+
216+
return await removeTypesFn(code);
217+
}
218+
219+
/**
220+
* Takes a substring of a string based on byte offsets.
221+
* @private
222+
* @method substringBytes
223+
* @param {string} value : The input string.
224+
* @param {number} start : The byte index of the substring start.
225+
* @param {number} end : The byte index of the substring end.
226+
* @return {string} : The substring.
227+
*/
228+
function substringBytes(value, start, end) {
229+
let buf = Buffer.from(value);
230+
return buf.subarray(start, end).toString();
231+
}

src/utils.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,24 @@ function replaceExtension(filePath, newExt) {
1010
});
1111
}
1212

13+
function replaceTypeScriptExtension(filePath) {
14+
const extensionMap = {
15+
'.ts': '.js',
16+
'.gts': '.gjs',
17+
};
18+
const ext = path.extname(filePath);
19+
const newExt = extensionMap[ext];
20+
21+
return replaceExtension(filePath, newExt);
22+
}
23+
1324
function isTypeScriptFile(filePath) {
14-
return path.extname(filePath) === '.ts';
25+
const extension = path.extname(filePath);
26+
return extension === '.ts' || extension === '.gts';
1527
}
1628

1729
module.exports = {
1830
replaceExtension,
31+
replaceTypeScriptExtension,
1932
isTypeScriptFile,
2033
};

test/generate.test.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@ const JS_FIXTURE = `export default function foo(a, b) {
1313
}
1414
`;
1515

16+
const GTS_FIXTURE = `import Component from '@glimmer/component';
17+
18+
interface Signature {
19+
Args: {
20+
foo: string;
21+
}
22+
}
23+
24+
export default class Foo extends Component<Signature> {
25+
bar: string = 'bar';
26+
<template>{{@foo}} {{this.bar}}</template>
27+
}
28+
`;
29+
30+
const GJS_FIXTURE = `import Component from '@glimmer/component';
31+
32+
export default class Foo extends Component {
33+
bar = 'bar';
34+
<template>{{@foo}} {{this.bar}}</template>
35+
}
36+
`;
37+
1638
const ROOT = process.cwd();
1739
const EmberCLITargets = ['ember-cli-3-24', 'ember-cli-3-28', 'ember-cli'];
1840

@@ -86,6 +108,19 @@ describe('ember generate', () => {
86108
return a + b;
87109
}
88110
`,
111+
'__name__.gts': `import Component from '@glimmer/component';
112+
113+
interface Signature {
114+
Args: {
115+
foo: string;
116+
}
117+
}
118+
119+
export default class <%=classifiedModuleName %> extends Component<Signature> {
120+
bar: string = 'bar';
121+
<template>{{@foo}} {{this.bar}}</template>
122+
}
123+
`
89124
},
90125
},
91126
},
@@ -98,16 +133,20 @@ describe('ember generate', () => {
98133
await ember(['generate', 'my-blueprint', 'foo']);
99134

100135
const generated = await file('app/my-blueprints/foo.js');
101-
102136
expect(generated).toEqual(JS_FIXTURE);
137+
138+
const generatedGjs = await file('app/my-blueprints/foo.gjs');
139+
expect(generatedGjs).toEqual(GJS_FIXTURE);
103140
});
104141

105142
test('it generates typescript with --typescript', async () => {
106143
await ember(['generate', 'my-blueprint', 'foo', '--typescript']);
107144

108145
const generated = await file('app/my-blueprints/foo.ts');
109-
110146
expect(generated).toEqual(TS_FIXTURE);
147+
148+
const generatedGts = await file('app/my-blueprints/foo.gts');
149+
expect(generatedGts).toEqual(GTS_FIXTURE);
111150
});
112151

113152
test('it generates typescript when isTypeScriptProject is true', async () => {
@@ -119,6 +158,9 @@ describe('ember generate', () => {
119158

120159
const generated = await file('app/my-blueprints/foo.ts');
121160
expect(generated).toEqual(TS_FIXTURE);
161+
162+
const generatedGts = await file('app/my-blueprints/foo.gts');
163+
expect(generatedGts).toEqual(GTS_FIXTURE);
122164
});
123165

124166
test('it generates javascript when isTypeScriptProject is explicitly false', async () => {
@@ -130,6 +172,9 @@ describe('ember generate', () => {
130172

131173
const generated = await file('app/my-blueprints/foo.js');
132174
expect(generated).toEqual(JS_FIXTURE);
175+
176+
const generatedGjs = await file('app/my-blueprints/foo.gjs');
177+
expect(generatedGjs).toEqual(GJS_FIXTURE);
133178
});
134179

135180
test('it generates typescript if {typescript: true} is present in ember-cli', async () => {
@@ -141,13 +186,19 @@ describe('ember generate', () => {
141186

142187
const generated = await file('app/my-blueprints/foo.ts');
143188
expect(generated).toEqual(TS_FIXTURE);
189+
190+
const generatedGts = await file('app/my-blueprints/foo.gts');
191+
expect(generatedGts).toEqual(GTS_FIXTURE);
144192
});
145193

146194
test('does not generate typescript when --no-typescript is passed', async () => {
147195
await ember(['generate', 'my-blueprint', 'foo', '--no-typescript']);
148196

149197
const generated = await file('app/my-blueprints/foo.js');
150198
expect(generated).toEqual(JS_FIXTURE);
199+
200+
const generatedGjs = await file('app/my-blueprints/foo.gjs');
201+
expect(generatedGjs).toEqual(GJS_FIXTURE);
151202
});
152203

153204
test('does not generate typescript when --no-typescript is passed, even in a typescript project', async () => {
@@ -159,6 +210,9 @@ describe('ember generate', () => {
159210

160211
const generated = await file('app/my-blueprints/foo.js');
161212
expect(generated).toEqual(JS_FIXTURE);
213+
214+
const generatedGjs = await file('app/my-blueprints/foo.gjs');
215+
expect(generatedGjs).toEqual(GJS_FIXTURE);
162216
});
163217
});
164218

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2445,6 +2445,11 @@ content-disposition@0.5.4:
24452445
dependencies:
24462446
safe-buffer "5.2.1"
24472447

2448+
content-tag@^2.0.2:
2449+
version "2.0.2"
2450+
resolved "https://registry.yarnpkg.com/content-tag/-/content-tag-2.0.2.tgz#978802d97df21516daa10d78e2a1f148e89eab8b"
2451+
integrity sha512-qHRyTp02dgzRK2tsCFxZ1H289bZOuSLNpupr6prvnSFq4SFPmNlBKbbE5PCMb+8+Z1a1z+yCVtXvQIGUCCa3lQ==
2452+
24482453
content-type@~1.0.4:
24492454
version "1.0.4"
24502455
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"

0 commit comments

Comments
 (0)