@embroider/compat 2.0.2 → 2.1.1-unstable.00ec2e7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +5 -4
  2. package/src/addon-dependency-rules/ember-data.js +1 -0
  3. package/src/addon-dependency-rules/ember-data.js.map +1 -1
  4. package/src/audit/babel-visitor.d.ts +1 -0
  5. package/src/audit/babel-visitor.js +2 -2
  6. package/src/audit/babel-visitor.js.map +1 -1
  7. package/src/audit/build.js +48 -46
  8. package/src/audit/build.js.map +1 -1
  9. package/src/audit/options.d.ts +3 -2
  10. package/src/audit/options.js.map +1 -1
  11. package/src/audit-cli.js +0 -0
  12. package/src/audit.d.ts +25 -1
  13. package/src/audit.js +55 -33
  14. package/src/audit.js.map +1 -1
  15. package/src/babel-plugin-adjust-imports.d.ts +16 -0
  16. package/src/babel-plugin-adjust-imports.js +198 -0
  17. package/src/babel-plugin-adjust-imports.js.map +1 -0
  18. package/src/compat-adapters/@glimmer/tracking.d.ts +0 -1
  19. package/src/compat-adapters/@glimmer/tracking.js +8 -15
  20. package/src/compat-adapters/@glimmer/tracking.js.map +1 -1
  21. package/src/compat-adapters/ember-data.d.ts +1 -0
  22. package/src/compat-adapters/ember-data.js +4 -0
  23. package/src/compat-adapters/ember-data.js.map +1 -1
  24. package/src/compat-app.js +41 -77
  25. package/src/compat-app.js.map +1 -1
  26. package/src/default-pipeline.d.ts +1 -1
  27. package/src/default-pipeline.js +3 -2
  28. package/src/default-pipeline.js.map +1 -1
  29. package/src/dependency-rules.d.ts +12 -9
  30. package/src/dependency-rules.js +21 -37
  31. package/src/dependency-rules.js.map +1 -1
  32. package/src/resolver-transform.d.ts +13 -4
  33. package/src/resolver-transform.js +722 -278
  34. package/src/resolver-transform.js.map +1 -1
  35. package/src/v1-app.js +19 -5
  36. package/src/v1-app.js.map +1 -1
  37. package/src/audit/capture.d.ts +0 -8
  38. package/src/audit/capture.js +0 -45
  39. package/src/audit/capture.js.map +0 -1
  40. package/src/resolver.d.ts +0 -128
  41. package/src/resolver.js +0 -677
  42. package/src/resolver.js.map +0 -1
@@ -1,83 +1,170 @@
1
1
  "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
2
8
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
10
  };
5
11
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const resolver_1 = __importDefault(require("./resolver"));
12
+ exports.builtInKeywords = void 0;
13
+ const dependency_rules_1 = require("./dependency-rules");
14
+ const typescript_memoize_1 = require("typescript-memoize");
7
15
  const assert_never_1 = __importDefault(require("assert-never"));
8
- // This is the AST transform that resolves components, helpers and modifiers at build time
9
- function makeResolverTransform({ resolver, patchHelpersBug }) {
10
- const resolverTransform = ({ filename, contents, meta: { jsutils }, syntax: { builders }, }) => {
11
- let scopeStack = new ScopeStack();
12
- let emittedAMDDeps = new Set();
13
- function emitAMD(dep) {
14
- if (dep && !emittedAMDDeps.has(dep.runtimeName)) {
15
- let parts = dep.runtimeName.split('/');
16
- let { path, runtimeName } = dep;
17
- jsutils.emitExpression(context => {
18
- let identifier = context.import(path, 'default', parts[parts.length - 1]);
19
- return `window.define("${runtimeName}", () => ${identifier})`;
20
- });
21
- emittedAMDDeps.add(dep.runtimeName);
22
- }
23
- }
24
- function emit(parentPath, resolution, setter) {
25
- switch (resolution === null || resolution === void 0 ? void 0 : resolution.type) {
26
- case 'error':
27
- resolver.reportError(resolution, filename, contents);
28
- return;
29
- case 'helper':
30
- if (patchHelpersBug) {
31
- // lexical invocation of helpers was not reliable before Ember 4.2 due to https://github.com/emberjs/ember.js/pull/19878
32
- emitAMD(resolution.module);
16
+ const path_1 = require("path");
17
+ const fs_extra_1 = require("fs-extra");
18
+ const dasherize_component_name_1 = require("./dasherize-component-name");
19
+ const core_1 = require("@embroider/core");
20
+ const lodash_1 = require("lodash");
21
+ exports.builtInKeywords = [
22
+ '-get-dynamic-var',
23
+ '-in-element',
24
+ '-with-dynamic-vars',
25
+ 'action',
26
+ 'array',
27
+ 'component',
28
+ 'concat',
29
+ 'debugger',
30
+ 'each-in',
31
+ 'each',
32
+ 'fn',
33
+ 'get',
34
+ 'has-block-params',
35
+ 'has-block',
36
+ 'hasBlock',
37
+ 'hasBlockParams',
38
+ 'hash',
39
+ 'helper',
40
+ 'if',
41
+ 'in-element',
42
+ 'input',
43
+ 'let',
44
+ 'link-to',
45
+ 'loc',
46
+ 'log',
47
+ 'modifier',
48
+ 'mount',
49
+ 'mut',
50
+ 'on',
51
+ 'outlet',
52
+ 'partial',
53
+ 'query-params',
54
+ 'readonly',
55
+ 'textarea',
56
+ 'unbound',
57
+ 'unique-id',
58
+ 'unless',
59
+ 'with',
60
+ 'yield',
61
+ ];
62
+ class TemplateResolver {
63
+ constructor(env, config) {
64
+ this.env = env;
65
+ this.config = config;
66
+ this.name = 'embroider-build-time-resolver';
67
+ this.scopeStack = new ScopeStack();
68
+ this.visitor = {
69
+ Template: {
70
+ enter: () => {
71
+ if (this.env.locals) {
72
+ this.scopeStack.pushMustacheBlock(this.env.locals);
33
73
  }
34
- else {
35
- setter(parentPath.node, builders.path(jsutils.bindImport(resolution.module.path, 'default', parentPath, { nameHint: resolution.nameHint })));
74
+ },
75
+ exit: () => {
76
+ if (this.env.locals) {
77
+ this.scopeStack.pop();
36
78
  }
79
+ },
80
+ },
81
+ Block: {
82
+ enter: node => {
83
+ this.scopeStack.pushMustacheBlock(node.blockParams);
84
+ },
85
+ exit: () => {
86
+ this.scopeStack.pop();
87
+ },
88
+ },
89
+ BlockStatement: (node, path) => {
90
+ if (node.path.type !== 'PathExpression') {
37
91
  return;
38
- case 'modifier':
39
- setter(parentPath.node, builders.path(jsutils.bindImport(resolution.module.path, 'default', parentPath, { nameHint: resolution.nameHint })));
92
+ }
93
+ let rootName = node.path.parts[0];
94
+ if (this.scopeStack.inScope(rootName, path)) {
40
95
  return;
41
- case 'component':
42
- // When people are using octane-style template co-location or
43
- // polaris-style first-class templates, we see only JS files for their
44
- // components, because the template association is handled before
45
- // we're doing any resolving here. In that case, we can safely do
46
- // component invocation via lexical scope.
47
- //
48
- // But when people are using the older non-co-located template style,
49
- // we can't safely do that -- ember needs to discover both the
50
- // component and the template in the AMD loader to associate them. In
51
- // that case, we emit just-in-time AMD definitions for them.
52
- if (resolution.jsModule && !resolution.hbsModule) {
53
- setter(parentPath.node, builders.path(jsutils.bindImport(resolution.jsModule.path, 'default', parentPath, { nameHint: resolution.nameHint })));
54
- }
55
- else {
56
- emitAMD(resolution.hbsModule);
57
- emitAMD(resolution.jsModule);
58
- }
59
- case undefined:
96
+ }
97
+ if (node.path.this === true) {
60
98
  return;
61
- default:
62
- (0, assert_never_1.default)(resolution);
63
- }
64
- }
65
- return {
66
- name: 'embroider-build-time-resolver',
67
- visitor: {
68
- Program: {
69
- enter(node) {
70
- scopeStack.push(node.blockParams);
71
- },
72
- exit() {
73
- scopeStack.pop();
74
- },
75
- },
76
- BlockStatement(node, path) {
99
+ }
100
+ if (node.path.parts.length > 1) {
101
+ // paths with a dot in them (which therefore split into more than
102
+ // one "part") are classically understood by ember to be contextual
103
+ // components, which means there's nothing to resolve at this
104
+ // location.
105
+ return;
106
+ }
107
+ if (node.path.original === 'component' && node.params.length > 0) {
108
+ let resolution = this.handleComponentHelper(node.params[0]);
109
+ this.emit(path, resolution, (node, newIdentifier) => {
110
+ node.params[0] = newIdentifier;
111
+ });
112
+ return;
113
+ }
114
+ let resolution = this.targetComponent(node.path.original);
115
+ this.emit(path, resolution, (node, newId) => {
116
+ node.path = newId;
117
+ });
118
+ if ((resolution === null || resolution === void 0 ? void 0 : resolution.type) === 'component') {
119
+ this.scopeStack.enteringComponentBlock(resolution, ({ argumentsAreComponents }) => {
120
+ this.handleDynamicComponentArguments(rootName, argumentsAreComponents, extendPath(extendPath(path, 'hash'), 'pairs'));
121
+ });
122
+ }
123
+ },
124
+ SubExpression: (node, path) => {
125
+ if (node.path.type !== 'PathExpression') {
126
+ return;
127
+ }
128
+ if (node.path.this === true) {
129
+ return;
130
+ }
131
+ if (this.scopeStack.inScope(node.path.parts[0], path)) {
132
+ return;
133
+ }
134
+ if (node.path.original === 'component' && node.params.length > 0) {
135
+ let resolution = this.handleComponentHelper(node.params[0]);
136
+ this.emit(path, resolution, (node, newId) => {
137
+ node.params[0] = newId;
138
+ });
139
+ return;
140
+ }
141
+ if (node.path.original === 'helper' && node.params.length > 0) {
142
+ let resolution = this.handleDynamicHelper(node.params[0]);
143
+ this.emit(path, resolution, (node, newId) => {
144
+ node.params[0] = newId;
145
+ });
146
+ return;
147
+ }
148
+ if (node.path.original === 'modifier' && node.params.length > 0) {
149
+ let resolution = this.handleDynamicModifier(node.params[0]);
150
+ this.emit(path, resolution, (node, newId) => {
151
+ node.params[0] = newId;
152
+ });
153
+ return;
154
+ }
155
+ let resolution = this.targetHelper(node.path.original);
156
+ this.emit(path, resolution, (node, newId) => {
157
+ node.path = newId;
158
+ });
159
+ },
160
+ MustacheStatement: {
161
+ enter: (node, path) => {
162
+ var _a;
77
163
  if (node.path.type !== 'PathExpression') {
78
164
  return;
79
165
  }
80
- if (scopeStack.inScope(node.path.parts[0])) {
166
+ let rootName = node.path.parts[0];
167
+ if (this.scopeStack.inScope(rootName, path)) {
81
168
  return;
82
169
  }
83
170
  if (node.path.this === true) {
@@ -90,183 +177,556 @@ function makeResolverTransform({ resolver, patchHelpersBug }) {
90
177
  // location.
91
178
  return;
92
179
  }
93
- if (node.path.original === 'component' && node.params.length > 0) {
94
- let resolution = handleComponentHelper(node.params[0], resolver, filename, scopeStack);
95
- emit(path, resolution, (node, newIdentifier) => {
96
- node.params[0] = newIdentifier;
97
- });
98
- return;
99
- }
100
- // a block counts as args from our perpsective (it's enough to prove
101
- // this thing must be a component, not content)
102
- let hasArgs = true;
103
- let resolution = resolver.resolveMustache(node.path.original, hasArgs, filename, node.path.loc);
104
- emit(path, resolution, (node, newId) => {
105
- node.path = newId;
106
- });
107
- if ((resolution === null || resolution === void 0 ? void 0 : resolution.type) === 'component') {
108
- scopeStack.enteringComponentBlock(resolution, ({ argumentsAreComponents }) => {
109
- let pairs = extendPath(extendPath(path, 'hash'), 'pairs');
110
- for (let name of argumentsAreComponents) {
111
- let pair = pairs.find(pair => pair.node.key === name);
112
- if (pair) {
113
- let resolution = handleComponentHelper(pair.node.value, resolver, filename, scopeStack, {
114
- componentName: node.path.original,
115
- argumentName: name,
116
- });
117
- emit(pair, resolution, (node, newId) => {
118
- node.value = newId;
119
- });
120
- }
121
- }
122
- });
123
- }
124
- },
125
- SubExpression(node, path) {
126
- if (node.path.type !== 'PathExpression') {
127
- return;
128
- }
129
- if (node.path.this === true) {
130
- return;
131
- }
132
- if (scopeStack.inScope(node.path.parts[0])) {
180
+ if (node.path.original.startsWith('@')) {
181
+ // similarly, global resolution of helpers and components never
182
+ // happens with argument paths (it could still be an invocation, but
183
+ // it would be a lexically-scoped invocation, not one we need to
184
+ // adjust)
133
185
  return;
134
186
  }
135
187
  if (node.path.original === 'component' && node.params.length > 0) {
136
- let resolution = handleComponentHelper(node.params[0], resolver, filename, scopeStack);
137
- emit(path, resolution, (node, newId) => {
188
+ let resolution = this.handleComponentHelper(node.params[0]);
189
+ this.emit(path, resolution, (node, newId) => {
138
190
  node.params[0] = newId;
139
191
  });
140
192
  return;
141
193
  }
142
194
  if (node.path.original === 'helper' && node.params.length > 0) {
143
- handleDynamicHelper(node.params[0], resolver, filename);
195
+ let resolution = this.handleDynamicHelper(node.params[0]);
196
+ this.emit(path, resolution, (node, newIdentifier) => {
197
+ node.params[0] = newIdentifier;
198
+ });
144
199
  return;
145
200
  }
146
- if (node.path.original === 'modifier' && node.params.length > 0) {
147
- handleDynamicModifier(node.params[0], resolver, filename);
201
+ if (((_a = path.parent) === null || _a === void 0 ? void 0 : _a.node.type) === 'AttrNode') {
202
+ // this mustache is the value of an attribute. Components aren't
203
+ // allowed here, so we're not ambiguous, so resolve a helper.
204
+ let resolution = this.targetHelper(node.path.original);
205
+ this.emit(path, resolution, (node, newIdentifier) => {
206
+ node.path = newIdentifier;
207
+ });
148
208
  return;
149
209
  }
150
- let resolution = resolver.resolveSubExpression(node.path.original, filename, node.path.loc);
151
- emit(path, resolution, (node, newId) => {
152
- node.path = newId;
210
+ let hasArgs = node.params.length > 0 || node.hash.pairs.length > 0;
211
+ let resolution = this.targetHelperOrComponent(node.path.original, node.path.loc, hasArgs);
212
+ this.emit(path, resolution, (node, newIdentifier) => {
213
+ node.path = newIdentifier;
153
214
  });
215
+ if ((resolution === null || resolution === void 0 ? void 0 : resolution.type) === 'component') {
216
+ this.handleDynamicComponentArguments(node.path.original, resolution.argumentsAreComponents, extendPath(extendPath(path, 'hash'), 'pairs'));
217
+ }
154
218
  },
155
- MustacheStatement: {
156
- enter(node, path) {
157
- if (node.path.type !== 'PathExpression') {
158
- return;
159
- }
160
- if (scopeStack.inScope(node.path.parts[0])) {
161
- return;
162
- }
163
- if (node.path.this === true) {
164
- return;
165
- }
166
- if (node.path.parts.length > 1) {
167
- // paths with a dot in them (which therefore split into more than
168
- // one "part") are classically understood by ember to be contextual
169
- // components, which means there's nothing to resolve at this
170
- // location.
171
- return;
172
- }
173
- if (node.path.original === 'component' && node.params.length > 0) {
174
- let resolution = handleComponentHelper(node.params[0], resolver, filename, scopeStack);
175
- emit(path, resolution, (node, newId) => {
176
- node.params[0] = newId;
177
- });
178
- return;
179
- }
180
- if (node.path.original === 'helper' && node.params.length > 0) {
181
- handleDynamicHelper(node.params[0], resolver, filename);
182
- return;
219
+ },
220
+ ElementModifierStatement: (node, path) => {
221
+ if (node.path.type !== 'PathExpression') {
222
+ return;
223
+ }
224
+ if (this.scopeStack.inScope(node.path.parts[0], path)) {
225
+ return;
226
+ }
227
+ if (node.path.this === true) {
228
+ return;
229
+ }
230
+ if (node.path.data === true) {
231
+ return;
232
+ }
233
+ if (node.path.parts.length > 1) {
234
+ // paths with a dot in them (which therefore split into more than
235
+ // one "part") are classically understood by ember to be contextual
236
+ // components. With the introduction of `Template strict mode` in Ember 3.25
237
+ // it is also possible to pass modifiers this way which means there's nothing
238
+ // to resolve at this location.
239
+ return;
240
+ }
241
+ let resolution = this.targetElementModifier(node.path.original);
242
+ this.emit(path, resolution, (node, newId) => {
243
+ node.path = newId;
244
+ });
245
+ },
246
+ ElementNode: {
247
+ enter: (node, path) => {
248
+ let rootName = node.tag.split('.')[0];
249
+ if (!this.scopeStack.inScope(rootName, path)) {
250
+ let resolution = null;
251
+ // if it starts with lower case, it can't be a component we need to
252
+ // globally resolve
253
+ if (node.tag[0] !== node.tag[0].toLowerCase()) {
254
+ resolution = this.targetComponent((0, dasherize_component_name_1.dasherize)(node.tag));
183
255
  }
184
- let hasArgs = node.params.length > 0 || node.hash.pairs.length > 0;
185
- let resolution = resolver.resolveMustache(node.path.original, hasArgs, filename, node.path.loc);
186
- emit(path, resolution, (node, newIdentifier) => {
187
- node.path = newIdentifier;
256
+ this.emit(path, resolution, (node, newId) => {
257
+ node.tag = newId.original;
188
258
  });
189
259
  if ((resolution === null || resolution === void 0 ? void 0 : resolution.type) === 'component') {
190
- let pairs = extendPath(extendPath(path, 'hash'), 'pairs');
191
- for (let name of resolution.argumentsAreComponents) {
192
- let pair = pairs.find(pair => pair.node.key === name);
193
- if (pair) {
194
- let resolution = handleComponentHelper(pair.node.value, resolver, filename, scopeStack, {
195
- componentName: node.path.original,
196
- argumentName: name,
197
- });
198
- emit(pair, resolution, (node, newId) => {
199
- node.value = newId;
200
- });
201
- }
202
- }
260
+ this.scopeStack.enteringComponentBlock(resolution, ({ argumentsAreComponents }) => {
261
+ this.handleDynamicComponentArguments(node.tag, argumentsAreComponents, extendPath(path, 'attributes'));
262
+ });
203
263
  }
204
- },
264
+ }
265
+ this.scopeStack.pushElementBlock(node.blockParams, node);
205
266
  },
206
- ElementModifierStatement(node, path) {
207
- if (node.path.type !== 'PathExpression') {
208
- return;
267
+ exit: () => {
268
+ this.scopeStack.pop();
269
+ },
270
+ },
271
+ };
272
+ this.moduleResolver = new core_1.Resolver(config);
273
+ if (globalThis.embroider_audit) {
274
+ this.auditHandler = globalThis.embroider_audit;
275
+ }
276
+ }
277
+ emit(parentPath, resolution, setter) {
278
+ switch (resolution === null || resolution === void 0 ? void 0 : resolution.type) {
279
+ case 'error':
280
+ this.reportError(resolution);
281
+ return;
282
+ case 'component':
283
+ case 'modifier':
284
+ case 'helper': {
285
+ let name = this.env.meta.jsutils.bindImport(resolution.specifier, 'default', parentPath, {
286
+ nameHint: resolution.nameHint,
287
+ });
288
+ setter(parentPath.node, this.env.syntax.builders.path(name));
289
+ return;
290
+ }
291
+ case undefined:
292
+ return;
293
+ default:
294
+ (0, assert_never_1.default)(resolution);
295
+ }
296
+ }
297
+ reportError(dep) {
298
+ if (!this.auditHandler && !this.config.options.allowUnsafeDynamicComponents) {
299
+ let e = new Error(`${dep.message}: ${dep.detail} in ${this.humanReadableFile(this.env.filename)}`);
300
+ e.isTemplateResolverError = true;
301
+ e.loc = dep.loc;
302
+ e.moduleName = this.env.filename;
303
+ throw e;
304
+ }
305
+ if (this.auditHandler) {
306
+ this.auditHandler({
307
+ message: dep.message,
308
+ filename: this.env.filename,
309
+ detail: dep.detail,
310
+ loc: dep.loc,
311
+ source: this.env.contents,
312
+ });
313
+ }
314
+ }
315
+ humanReadableFile(file) {
316
+ let { appRoot } = this.config;
317
+ if (!appRoot.endsWith(path_1.sep)) {
318
+ appRoot += path_1.sep;
319
+ }
320
+ if (file.startsWith(appRoot)) {
321
+ return file.slice(appRoot.length);
322
+ }
323
+ return file;
324
+ }
325
+ handleComponentHelper(param, impliedBecause) {
326
+ let locator;
327
+ switch (param.type) {
328
+ case 'StringLiteral':
329
+ locator = { type: 'literal', path: param.value };
330
+ break;
331
+ case 'PathExpression':
332
+ locator = { type: 'path', path: param.original };
333
+ break;
334
+ case 'MustacheStatement':
335
+ if (param.hash.pairs.length === 0 && param.params.length === 0) {
336
+ return this.handleComponentHelper(param.path, impliedBecause);
337
+ }
338
+ else if (param.path.type === 'PathExpression' && param.path.original === 'component') {
339
+ // safe because we will handle this inner `{{component ...}}` mustache on its own
340
+ return null;
341
+ }
342
+ else {
343
+ locator = { type: 'other' };
344
+ }
345
+ break;
346
+ case 'TextNode':
347
+ locator = { type: 'literal', path: param.chars };
348
+ break;
349
+ case 'SubExpression':
350
+ if (param.path.type === 'PathExpression' && param.path.original === 'component') {
351
+ // safe because we will handle this inner `(component ...)` subexpression on its own
352
+ return null;
353
+ }
354
+ if (param.path.type === 'PathExpression' && param.path.original === 'ensure-safe-component') {
355
+ // safe because we trust ensure-safe-component
356
+ return null;
357
+ }
358
+ locator = { type: 'other' };
359
+ break;
360
+ default:
361
+ locator = { type: 'other' };
362
+ }
363
+ if (locator.type === 'path' && this.scopeStack.safeComponentInScope(locator.path)) {
364
+ return null;
365
+ }
366
+ return this.targetComponentHelper(locator, param.loc, impliedBecause);
367
+ }
368
+ handleDynamicComponentArguments(componentName, argumentsAreComponents, attributes) {
369
+ for (let name of argumentsAreComponents) {
370
+ let attr = attributes.find(attr => {
371
+ if (attr.node.type === 'AttrNode') {
372
+ return attr.node.name === '@' + name;
373
+ }
374
+ else {
375
+ return attr.node.key === name;
376
+ }
377
+ });
378
+ if (attr) {
379
+ let resolution = this.handleComponentHelper(attr.node.value, {
380
+ componentName,
381
+ argumentName: name,
382
+ });
383
+ this.emit(attr, resolution, (node, newId) => {
384
+ if (node.type === 'AttrNode') {
385
+ node.value = this.env.syntax.builders.mustache(newId);
209
386
  }
210
- if (scopeStack.inScope(node.path.parts[0])) {
211
- return;
387
+ else {
388
+ node.value = newId;
212
389
  }
213
- if (node.path.this === true) {
214
- return;
390
+ });
391
+ }
392
+ }
393
+ }
394
+ get staticComponentsEnabled() {
395
+ return this.config.options.staticComponents || Boolean(this.auditHandler);
396
+ }
397
+ get staticHelpersEnabled() {
398
+ return this.config.options.staticHelpers || Boolean(this.auditHandler);
399
+ }
400
+ get staticModifiersEnabled() {
401
+ return this.config.options.staticModifiers || Boolean(this.auditHandler);
402
+ }
403
+ isIgnoredComponent(dasherizedName) {
404
+ var _a;
405
+ return (_a = this.rules.components.get(dasherizedName)) === null || _a === void 0 ? void 0 : _a.safeToIgnore;
406
+ }
407
+ get rules() {
408
+ // rules that are keyed by the filename they're talking about
409
+ let files = new Map();
410
+ // rules that are keyed by our dasherized interpretation of the component's name
411
+ let components = new Map();
412
+ // we're not responsible for filtering out rules for inactive packages here,
413
+ // that is done before getting to us. So we should assume these are all in
414
+ // force.
415
+ for (let rule of this.config.activePackageRules) {
416
+ if (rule.components) {
417
+ for (let [snippet, rules] of Object.entries(rule.components)) {
418
+ let processedRules = (0, dependency_rules_1.preprocessComponentRule)(rules);
419
+ let dasherizedName = this.standardDasherize(snippet, rule);
420
+ components.set(dasherizedName, processedRules);
421
+ if (rules.layout) {
422
+ for (let root of rule.roots) {
423
+ if (rules.layout.addonPath) {
424
+ files.set((0, path_1.join)(root, rules.layout.addonPath), processedRules);
425
+ }
426
+ if (rules.layout.appPath) {
427
+ files.set((0, path_1.join)(root, rules.layout.appPath), processedRules);
428
+ }
429
+ }
215
430
  }
216
- if (node.path.data === true) {
217
- return;
431
+ }
432
+ }
433
+ if (rule.appTemplates) {
434
+ for (let [path, templateRules] of Object.entries(rule.appTemplates)) {
435
+ let processedRules = (0, dependency_rules_1.preprocessComponentRule)(templateRules);
436
+ for (let root of rule.roots) {
437
+ files.set((0, path_1.join)((0, dependency_rules_1.appTreeRulesDir)(root, this.moduleResolver), path), processedRules);
218
438
  }
219
- if (node.path.parts.length > 1) {
220
- // paths with a dot in them (which therefore split into more than
221
- // one "part") are classically understood by ember to be contextual
222
- // components. With the introduction of `Template strict mode` in Ember 3.25
223
- // it is also possible to pass modifiers this way which means there's nothing
224
- // to resolve at this location.
225
- return;
439
+ }
440
+ }
441
+ if (rule.addonTemplates) {
442
+ for (let [path, templateRules] of Object.entries(rule.addonTemplates)) {
443
+ let processedRules = (0, dependency_rules_1.preprocessComponentRule)(templateRules);
444
+ for (let root of rule.roots) {
445
+ files.set((0, path_1.join)(root, path), processedRules);
226
446
  }
227
- let resolution = resolver.resolveElementModifierStatement(node.path.original, filename, node.path.loc);
228
- emit(path, resolution, (node, newId) => {
229
- node.path = newId;
230
- });
231
- },
232
- ElementNode: {
233
- enter(node, path) {
234
- if (!scopeStack.inScope(node.tag.split('.')[0])) {
235
- const resolution = resolver.resolveElement(node.tag, filename, node.loc);
236
- emit(path, resolution, (node, newId) => {
237
- node.tag = newId.original;
238
- });
239
- if ((resolution === null || resolution === void 0 ? void 0 : resolution.type) === 'component') {
240
- scopeStack.enteringComponentBlock(resolution, ({ argumentsAreComponents }) => {
241
- let attributes = extendPath(path, 'attributes');
242
- for (let name of argumentsAreComponents) {
243
- let attr = attributes.find(attr => attr.node.name === '@' + name);
244
- if (attr) {
245
- let resolution = handleComponentHelper(attr.node.value, resolver, filename, scopeStack, {
246
- componentName: node.tag,
247
- argumentName: name,
248
- });
249
- emit(attr, resolution, (node, newId) => {
250
- node.value = builders.mustache(newId);
251
- });
252
- }
253
- }
254
- });
255
- }
256
- }
257
- scopeStack.push(node.blockParams);
258
- },
259
- exit() {
260
- scopeStack.pop();
261
- },
262
- },
263
- },
447
+ }
448
+ }
449
+ }
450
+ return { files, components };
451
+ }
452
+ findRules(absPath) {
453
+ let fileRules = this.rules.files.get(absPath);
454
+ let componentRules;
455
+ let componentName = this.moduleResolver.reverseComponentLookup(absPath);
456
+ if (componentName) {
457
+ componentRules = this.rules.components.get(componentName);
458
+ }
459
+ if (fileRules && componentRules) {
460
+ return (0, lodash_1.mergeWith)(fileRules, componentRules, appendArrays);
461
+ }
462
+ return fileRules !== null && fileRules !== void 0 ? fileRules : componentRules;
463
+ }
464
+ standardDasherize(snippet, rule) {
465
+ let name = (0, dasherize_component_name_1.snippetToDasherizedName)(snippet);
466
+ if (name == null) {
467
+ throw new Error(`unable to parse component snippet "${snippet}" from rule ${JSON.stringify(rule, null, 2)}`);
468
+ }
469
+ return name;
470
+ }
471
+ targetComponent(name) {
472
+ if (!this.staticComponentsEnabled) {
473
+ return null;
474
+ }
475
+ if (exports.builtInKeywords.includes(name)) {
476
+ return null;
477
+ }
478
+ if (this.isIgnoredComponent(name)) {
479
+ return null;
480
+ }
481
+ let componentRules = this.rules.components.get(name);
482
+ return {
483
+ type: 'component',
484
+ specifier: `#embroider_compat/components/${name}`,
485
+ yieldsComponents: componentRules ? componentRules.yieldsSafeComponents : [],
486
+ yieldsArguments: componentRules ? componentRules.yieldsArguments : [],
487
+ argumentsAreComponents: componentRules ? componentRules.argumentsAreComponents : [],
488
+ nameHint: this.nameHint(name),
264
489
  };
490
+ }
491
+ targetComponentHelper(component, loc, impliedBecause) {
492
+ if (!this.staticComponentsEnabled) {
493
+ return null;
494
+ }
495
+ let message;
496
+ if (impliedBecause) {
497
+ message = `argument "${impliedBecause.argumentName}" to component "${impliedBecause.componentName}" is treated as a component, but the value you're passing is dynamic`;
498
+ }
499
+ else {
500
+ message = `Unsafe dynamic component`;
501
+ }
502
+ if (component.type === 'other') {
503
+ return {
504
+ type: 'error',
505
+ message,
506
+ detail: `cannot statically analyze this expression`,
507
+ loc,
508
+ };
509
+ }
510
+ if (component.type === 'path') {
511
+ let ownComponentRules = this.findRules(this.env.filename);
512
+ if (ownComponentRules && ownComponentRules.safeInteriorPaths.includes(component.path)) {
513
+ return null;
514
+ }
515
+ return {
516
+ type: 'error',
517
+ message,
518
+ detail: component.path,
519
+ loc,
520
+ };
521
+ }
522
+ return this.targetComponent(component.path);
523
+ }
524
+ targetHelper(path) {
525
+ if (!this.staticHelpersEnabled) {
526
+ return null;
527
+ }
528
+ // people are not allowed to override the built-in helpers with their own
529
+ // globally-named helpers. It throws an error. So it's fine for us to
530
+ // prioritize the builtIns here without bothering to resolve a user helper
531
+ // of the same name.
532
+ if (exports.builtInKeywords.includes(path)) {
533
+ return null;
534
+ }
535
+ return {
536
+ type: 'helper',
537
+ specifier: `#embroider_compat/helpers/${path}`,
538
+ nameHint: this.nameHint(path),
539
+ };
540
+ }
541
+ targetHelperOrComponent(path, loc, hasArgs) {
542
+ /*
543
+
544
+ In earlier embroider versions we would do a bunch of module resolution right
545
+ here inside the ast transform to try to resolve the ambiguity of this case
546
+ and if we didn't find anything, leave the template unchanged. But that leads
547
+ to both a lot of extra build-time expense (since we are attempting
548
+ resolution for lots of things that may in fact be just some data and not a
549
+ component invocation at all, and also since we are pre-resolving modules
550
+ that will get resolved a second time by the final stage packager).
551
+
552
+ Now, we're going to be less forgiving, because it streamlines the build for
553
+ everyone who's not still using these *extremely* old patterns.
554
+
555
+ The problematic case is:
556
+
557
+ 1. In a non-strict template (because this whole resolver-transform.ts is a
558
+ no-op on strict handlebars).
559
+
560
+ 2. Have a mustache statement like: `{{something}}`, where `something` is:
561
+
562
+ a. Not a variable in scope (for example, there's no preceeding line
563
+ like `<Parent as |something|>`)
564
+ b. Does not start with `@` because that must be an argument from outside this template.
565
+ c. Does not contain a dot, like `some.thing` (because that case is classically
566
+ never a global component resolution that we would need to handle)
567
+ d. Does not start with `this` (this rule is mostly redundant with the previous rule,
568
+ but even a standalone `this` is never a component invocation).
569
+ e. Does not have any arguments. If there are argument like `{{something a=b}}`,
570
+ there is still ambiguity between helper vs component, but there is no longer
571
+ the possibility that this was just rendering some data.
572
+ f. Does not take a block, like `{{#something}}{{/something}}` (because that is
573
+ always a component, no ambiguity.)
574
+
575
+ We can't tell if this problematic case is really:
576
+
577
+ 1. A helper invocation with no arguments that is being directly rendered.
578
+ Out-of-the-box, ember already generates [a lint
579
+ error](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-curly-component-invocation.md)
580
+ for this, although it tells you to whitelist your helper when IMO it
581
+ should tell you to use an unambiguous syntax like `{{ (something) }}`
582
+ instead.
583
+
584
+ 2. A component invocation, which you could have written `<Something />`
585
+ instead. Angle-bracket invocation has been available and easy-to-adopt
586
+ for a very long time.
587
+
588
+ 3. Property-this-fallback for `{{this.something}}`. Property-this-fallback
589
+ is eliminated at Ember 4.0, so people have been heavily pushed to get
590
+ it out of their addons.
591
+ */
592
+ // first, bail out on all the stuff we can obviously ignore
593
+ if ((!this.staticHelpersEnabled && !this.staticComponentsEnabled) ||
594
+ exports.builtInKeywords.includes(path) ||
595
+ this.isIgnoredComponent(path)) {
596
+ return null;
597
+ }
598
+ let ownComponentRules = this.findRules(this.env.filename);
599
+ if (ownComponentRules === null || ownComponentRules === void 0 ? void 0 : ownComponentRules.disambiguate[path]) {
600
+ switch (ownComponentRules.disambiguate[path]) {
601
+ case 'component':
602
+ return this.targetComponent(path);
603
+ case 'helper':
604
+ return this.targetHelper(path);
605
+ case 'data':
606
+ return null;
607
+ }
608
+ }
609
+ if (!hasArgs && !path.includes('/') && !path.includes('@')) {
610
+ // this is the case that could also be property-this-fallback. We're going
611
+ // to force people to disambiguate, because letting a potential component
612
+ // or helper invocation lurk inside every bit of data you render is not
613
+ // ok.
614
+ this.reportError({
615
+ type: 'error',
616
+ message: 'unsupported ambiguous syntax',
617
+ detail: `"{{${path}}}" is ambiguous and could mean "{{this.${path}}}" or component "<${capitalize((0, lodash_1.camelCase)(path))} />" or helper "{{ (${path}) }}". Change it to one of those unambigous forms, or use a "disambiguate" packageRule to work around the problem if its in third-party code you cannot easily fix.`,
618
+ loc,
619
+ });
620
+ return null;
621
+ }
622
+ // Above we already bailed out if both of these were disabled, so we know at
623
+ // least one is turned on. If both aren't turned on, we're stuck, because we
624
+ // can't even tell if this *is* a component vs a helper.
625
+ if (!this.staticHelpersEnabled || !this.staticComponentsEnabled) {
626
+ this.reportError({
627
+ type: 'error',
628
+ message: 'unsupported ambiguity between helper and component',
629
+ detail: `this use of "{{${path}}}" could be helper "{{ (${path}) }}" or component "<${capitalize((0, lodash_1.camelCase)(path))} />", and your settings for staticHelpers and staticComponents do not agree. Either switch to one of the unambiguous forms, or make staticHelpers and staticComponents agree, or use a "disambiguate" packageRule to work around the problem if its in third-party code you cannot easily fix.`,
630
+ loc,
631
+ });
632
+ return null;
633
+ }
634
+ let componentRules = this.rules.components.get(path);
635
+ return {
636
+ type: 'component',
637
+ specifier: `#embroider_compat/ambiguous/${path}`,
638
+ yieldsComponents: componentRules ? componentRules.yieldsSafeComponents : [],
639
+ yieldsArguments: componentRules ? componentRules.yieldsArguments : [],
640
+ argumentsAreComponents: componentRules ? componentRules.argumentsAreComponents : [],
641
+ nameHint: this.nameHint(path),
642
+ };
643
+ }
644
+ targetElementModifier(path) {
645
+ if (!this.staticModifiersEnabled) {
646
+ return null;
647
+ }
648
+ if (exports.builtInKeywords.includes(path)) {
649
+ return null;
650
+ }
651
+ return {
652
+ type: 'modifier',
653
+ specifier: `#embroider_compat/modifiers/${path}`,
654
+ nameHint: this.nameHint(path),
655
+ };
656
+ }
657
+ targetDynamicModifier(modifier, loc) {
658
+ if (!this.staticModifiersEnabled) {
659
+ return null;
660
+ }
661
+ if (modifier.type === 'literal') {
662
+ return this.targetElementModifier(modifier.path);
663
+ }
664
+ else {
665
+ return {
666
+ type: 'error',
667
+ message: 'Unsafe dynamic modifier',
668
+ detail: `cannot statically analyze this expression`,
669
+ loc,
670
+ };
671
+ }
672
+ }
673
+ targetDynamicHelper(helper) {
674
+ if (!this.staticHelpersEnabled) {
675
+ return null;
676
+ }
677
+ if (helper.type === 'literal') {
678
+ return this.targetHelper(helper.path);
679
+ }
680
+ // we don't have to manage any errors in this case because ember itself
681
+ // considers it an error to pass anything but a string literal to the
682
+ // `helper` helper.
683
+ return null;
684
+ }
685
+ nameHint(path) {
686
+ let parts = path.split('@');
687
+ // the extra underscore here guarantees that we will never collide with an
688
+ // HTML element.
689
+ return parts[parts.length - 1] + '_';
690
+ }
691
+ handleDynamicModifier(param) {
692
+ if (param.type === 'StringLiteral') {
693
+ return this.targetDynamicModifier({ type: 'literal', path: param.value }, param.loc);
694
+ }
695
+ // we don't have to manage any errors in this case because ember itself
696
+ // considers it an error to pass anything but a string literal to the
697
+ // modifier helper.
698
+ return null;
699
+ }
700
+ handleDynamicHelper(param) {
701
+ // We only need to handle StringLiterals since Ember already throws an error if unsupported values
702
+ // are passed to the helper keyword.
703
+ // If a helper reference is passed in we don't need to do anything since it's either the result of a previous
704
+ // helper keyword invocation, or a helper reference that was imported somewhere.
705
+ if (param.type === 'StringLiteral') {
706
+ return this.targetDynamicHelper({ type: 'literal', path: param.value });
707
+ }
708
+ return null;
709
+ }
710
+ }
711
+ __decorate([
712
+ (0, typescript_memoize_1.Memoize)()
713
+ ], TemplateResolver.prototype, "rules", null);
714
+ // This is the AST transform that resolves components, helpers and modifiers at build time
715
+ function makeResolverTransform({ appRoot }) {
716
+ let config = (0, fs_extra_1.readJSONSync)((0, path_1.join)(appRoot, '.embroider', 'resolver.json'));
717
+ const resolverTransform = env => {
718
+ if (env.strict) {
719
+ return {
720
+ name: 'embroider-build-time-resolver-strict-noop',
721
+ visitor: {},
722
+ };
723
+ }
724
+ return new TemplateResolver(env, config);
265
725
  };
266
726
  resolverTransform.parallelBabel = {
267
727
  requireFile: __filename,
268
728
  buildUsing: 'makeResolverTransform',
269
- params: resolver_1.default,
729
+ params: { appRoot: appRoot },
270
730
  };
271
731
  return resolverTransform;
272
732
  }
@@ -275,10 +735,25 @@ class ScopeStack {
275
735
  constructor() {
276
736
  this.stack = [];
277
737
  }
278
- // as we enter a block, we push the block params onto here to mark them as
279
- // being in scope
280
- push(blockParams) {
281
- this.stack.unshift({ type: 'blockParams', blockParams });
738
+ // mustache blocks like:
739
+ //
740
+ // {{#stuff as |some block vars|}}
741
+ //
742
+ // are relatively simple for us because there's a dedicated `Block` AST node
743
+ // that exactly covers the range in which the variables are in scope.
744
+ pushMustacheBlock(blockParams) {
745
+ this.stack.unshift({ type: 'mustache', blockParams });
746
+ }
747
+ // element blocks like:
748
+ //
749
+ // <Stuff as |some block vars|>
750
+ //
751
+ // are *not* so simple for us because there's no single AST node that exactly
752
+ // covers the range in which the variables are in scope. For example, the
753
+ // *attributes* of the element do not see the variables, but the children of
754
+ // the element do.
755
+ pushElementBlock(blockParams, childrenOf) {
756
+ this.stack.unshift({ type: 'element', blockParams, childrenOf });
282
757
  }
283
758
  // and when we leave the block they go out of scope. If this block was tagged
284
759
  // by a safe component marker, we also clear that.
@@ -301,9 +776,14 @@ class ScopeStack {
301
776
  exit,
302
777
  });
303
778
  }
304
- inScope(name) {
779
+ inScope(name, fromPath) {
305
780
  for (let scope of this.stack) {
306
- if (scope.type === 'blockParams' && scope.blockParams.includes(name)) {
781
+ if (scope.type === 'mustache' && scope.blockParams.includes(name)) {
782
+ return true;
783
+ }
784
+ if (scope.type === 'element' &&
785
+ scope.blockParams.includes(name) &&
786
+ withinElementBlock(fromPath, scope.childrenOf)) {
307
787
  return true;
308
788
  }
309
789
  }
@@ -320,7 +800,7 @@ class ScopeStack {
320
800
  for (let i = 0; i < this.stack.length - 1; i++) {
321
801
  let here = this.stack[i];
322
802
  let next = this.stack[i + 1];
323
- if (here.type === 'blockParams' && next.type === 'componentBlockMarker') {
803
+ if ((here.type === 'mustache' || here.type === 'element') && next.type === 'componentBlockMarker') {
324
804
  let positionalIndex = here.blockParams.indexOf(parts[0]);
325
805
  if (positionalIndex === -1) {
326
806
  continue;
@@ -357,63 +837,6 @@ class ScopeStack {
357
837
  return false;
358
838
  }
359
839
  }
360
- function handleComponentHelper(param, resolver, moduleName, scopeStack, impliedBecause) {
361
- let locator;
362
- switch (param.type) {
363
- case 'StringLiteral':
364
- locator = { type: 'literal', path: param.value };
365
- break;
366
- case 'PathExpression':
367
- locator = { type: 'path', path: param.original };
368
- break;
369
- case 'MustacheStatement':
370
- if (param.hash.pairs.length === 0 && param.params.length === 0) {
371
- return handleComponentHelper(param.path, resolver, moduleName, scopeStack, impliedBecause);
372
- }
373
- else if (param.path.type === 'PathExpression' && param.path.original === 'component') {
374
- // safe because we will handle this inner `{{component ...}}` mustache on its own
375
- return null;
376
- }
377
- else {
378
- locator = { type: 'other' };
379
- }
380
- break;
381
- case 'TextNode':
382
- locator = { type: 'literal', path: param.chars };
383
- break;
384
- case 'SubExpression':
385
- if (param.path.type === 'PathExpression' && param.path.original === 'component') {
386
- // safe because we will handle this inner `(component ...)` subexpression on its own
387
- return null;
388
- }
389
- if (param.path.type === 'PathExpression' && param.path.original === 'ensure-safe-component') {
390
- // safe because we trust ensure-safe-component
391
- return null;
392
- }
393
- locator = { type: 'other' };
394
- break;
395
- default:
396
- locator = { type: 'other' };
397
- }
398
- if (locator.type === 'path' && scopeStack.safeComponentInScope(locator.path)) {
399
- return null;
400
- }
401
- return resolver.resolveComponentHelper(locator, moduleName, param.loc, impliedBecause);
402
- }
403
- function handleDynamicHelper(param, resolver, moduleName) {
404
- // We only need to handle StringLiterals since Ember already throws an error if unsupported values
405
- // are passed to the helper keyword.
406
- // If a helper reference is passed in we don't need to do anything since it's either the result of a previous
407
- // helper keyword invocation, or a helper reference that was imported somewhere.
408
- if (param.type === 'StringLiteral') {
409
- resolver.resolveDynamicHelper({ type: 'literal', path: param.value }, moduleName, param.loc);
410
- }
411
- }
412
- function handleDynamicModifier(param, resolver, moduleName) {
413
- if (param.type === 'StringLiteral') {
414
- resolver.resolveDynamicModifier({ type: 'literal', path: param.value }, moduleName, param.loc);
415
- }
416
- }
417
840
  function extendPath(path, key) {
418
841
  const _WalkerPath = path.constructor;
419
842
  let child = path.node[key];
@@ -424,4 +847,25 @@ function extendPath(path, key) {
424
847
  return new _WalkerPath(child, path, key);
425
848
  }
426
849
  }
850
+ function capitalize(word) {
851
+ return word[0].toUpperCase() + word.slice(1);
852
+ }
853
+ // ElementNodes have both children and attributes and both of those are
854
+ // "children" in the abstract syntax tree sense, but here we want to distinguish
855
+ // between them.
856
+ function withinElementBlock(childPath, ancestorNode) {
857
+ let cursor = childPath;
858
+ while (cursor && cursor.node !== ancestorNode) {
859
+ if (ancestorNode.children.includes(cursor.node)) {
860
+ return true;
861
+ }
862
+ cursor = cursor.parent;
863
+ }
864
+ return false;
865
+ }
866
+ function appendArrays(objValue, srcValue) {
867
+ if (Array.isArray(objValue)) {
868
+ return objValue.concat(srcValue);
869
+ }
870
+ }
427
871
  //# sourceMappingURL=resolver-transform.js.map