Skip to content

Commit 806ba5a

Browse files
Merge pull request #341 from dfreeman/embedded-template-support
Support transforming templates embedded in JS/TS files
2 parents 989c107 + 7440d06 commit 806ba5a

File tree

6 files changed

+150
-16
lines changed

6 files changed

+150
-16
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { hbs as echHBS } from 'ember-cli-htmlbars';
2+
import hipHBS from 'htmlbars-inline-precompile';
3+
import echipHBS from 'ember-cli-htmlbars-inline-precompile';
4+
import { hbs } from 'unknown-tag-source';
5+
6+
echHBS`
7+
Hello,
8+
{{target}}!
9+
\n
10+
`;
11+
12+
hipHBS`Hello, {{target}}!`;
13+
14+
echipHBS`Hello, {{target}}!`;
15+
16+
hbs`Hello, {{target}}!`;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { hbs as echHBS } from 'ember-cli-htmlbars';
2+
import hipHBS from 'htmlbars-inline-precompile';
3+
import echipHBS from 'ember-cli-htmlbars-inline-precompile';
4+
import { hbs } from 'unknown-tag-source';
5+
6+
echHBS`
7+
Hello,
8+
{{this.target}}!
9+
\n
10+
`;
11+
12+
hipHBS`Hello, {{this.target}}!`;
13+
14+
echipHBS`Hello, {{this.target}}!`;
15+
16+
hbs`Hello, {{target}}!`;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { hbs as echHBS } from 'ember-cli-htmlbars';
2+
import hipHBS from 'htmlbars-inline-precompile';
3+
import echipHBS from 'ember-cli-htmlbars-inline-precompile';
4+
5+
declare const hbs: unknown;
6+
7+
echHBS`
8+
Hello,
9+
{{target}}!
10+
\n
11+
`;
12+
13+
hipHBS`Hello, {{target}}!`;
14+
15+
echipHBS`Hello, {{target}}!`;
16+
17+
hbs`Hello, {{target}}!`;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { hbs as echHBS } from 'ember-cli-htmlbars';
2+
import hipHBS from 'htmlbars-inline-precompile';
3+
import echipHBS from 'ember-cli-htmlbars-inline-precompile';
4+
5+
declare const hbs: unknown;
6+
7+
echHBS`
8+
Hello,
9+
{{this.target}}!
10+
\n
11+
`;
12+
13+
hipHBS`Hello, {{this.target}}!`;
14+
15+
echipHBS`Hello, {{this.target}}!`;
16+
17+
hbs`Hello, {{target}}!`;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const TEMPLATE_TAG_IMPORTS = [
2+
{ source: 'ember-cli-htmlbars', name: 'hbs' },
3+
{ source: 'htmlbars-inline-precompile', name: 'default' },
4+
{ source: 'ember-cli-htmlbars-inline-precompile', name: 'default' },
5+
];
6+
7+
// Identifies whether a TaggedTemplateExpression corresponds to an Ember template
8+
// using one of a known set of `hbs` tags.
9+
exports.isEmberTemplate = function isEmberTemplate(path) {
10+
let tag = path.get('tag');
11+
let hasInterpolation = path.node.quasi.quasis.length !== 1;
12+
let isKnownTag = TEMPLATE_TAG_IMPORTS.some(({ source, name }) =>
13+
isImportReference(tag, source, name)
14+
);
15+
16+
return isKnownTag && !hasInterpolation;
17+
};
18+
19+
// Determines whether the given identifier is a reference to an export
20+
// from a particular module.
21+
function isImportReference(path, importSource, importName) {
22+
let scope = path.scope.lookup(path.node.name);
23+
let bindings = scope ? scope.getBindings() : {};
24+
let bindingIdentifiers = bindings[path.node.name] || [];
25+
26+
for (let binding of bindingIdentifiers) {
27+
let specifier = binding.parent.node;
28+
let importDeclaration = binding.parent.parent.node;
29+
let bindingImportedName =
30+
specifier.type === 'ImportDefaultSpecifier'
31+
? 'default'
32+
: specifier.type === 'ImportSpecifier'
33+
? specifier.imported.name
34+
: null;
35+
36+
if (bindingImportedName === importName && importDeclaration.source.value === importSource) {
37+
return true;
38+
}
39+
}
40+
41+
return false;
42+
}

transforms/no-implicit-this/index.js

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ const debug = require('debug')('ember-no-implicit-this-codemod:transform');
55
const recast = require('ember-template-recast');
66
const { getTelemetry } = require('ember-codemods-telemetry-helpers');
77
const transform = require('./helpers/plugin');
8-
const { getOptions: getCLIOptions } = require('codemod-cli');
8+
const { getOptions: getCLIOptions, jscodeshift } = require('codemod-cli');
9+
const { isEmberTemplate } = require('./helpers/tagged-templates');
910
const DEFAULT_OPTIONS = {};
1011

1112
/**
@@ -41,23 +42,48 @@ function getOptions() {
4142
return options;
4243
}
4344

44-
module.exports = function transformer(file /*, api */) {
45-
let extension = path.extname(file.path);
46-
let options = Object.assign({}, DEFAULT_OPTIONS, getOptions());
47-
48-
if (!['.hbs'].includes(extension.toLowerCase())) {
49-
debug('Skipping %s because it does not match the .hbs file extension', file.path);
50-
51-
// do nothing on non-hbs files
52-
return;
53-
}
54-
55-
debug('Parsing %s ...', file.path);
56-
let root = recast.parse(file.source);
45+
/**
46+
* Given the location and source text of a template, as well as codemod options,
47+
* returns the rewritten template contents with `this` references inserted where
48+
* necessary.
49+
*/
50+
function rewriteTemplate(path, source, options) {
51+
debug('Parsing %s ...', path);
52+
let root = recast.parse(source);
5753

58-
debug('Transforming %s ...', file.path);
54+
debug('Transforming %s ...', path);
5955
transform(root, options);
6056

61-
debug('Generating new content for %s ...', file.path);
57+
debug('Generating new content for %s ...', path);
6258
return recast.print(root);
59+
}
60+
61+
/**
62+
* Given a JS or TS file that potentially has embedded templates within it,
63+
* returns updated source with those templates rewritten to include `this`
64+
* references where needed.
65+
*/
66+
function rewriteEmbeddedTemplates(file, options, api) {
67+
return jscodeshift
68+
.getParser(api)(file.source)
69+
.find('TaggedTemplateExpression', { tag: { type: 'Identifier' } })
70+
.forEach(path => {
71+
if (isEmberTemplate(path)) {
72+
let { value } = path.node.quasi.quasis[0];
73+
value.raw = rewriteTemplate(file.path, value.raw, options);
74+
}
75+
})
76+
.toSource();
77+
}
78+
79+
module.exports = function transformer(file, api) {
80+
let extension = path.extname(file.path).toLowerCase();
81+
let options = Object.assign({}, DEFAULT_OPTIONS, getOptions());
82+
if (extension === '.hbs') {
83+
return rewriteTemplate(file.path, file.source, options);
84+
} else if (extension === '.js' || extension === '.ts') {
85+
return rewriteEmbeddedTemplates(file, options, api);
86+
} else {
87+
debug('Skipping %s because it does not match a known extension with templates', file.path);
88+
}
6389
};

0 commit comments

Comments
 (0)