@jsenv/core 41.4.4 → 41.4.5

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.
@@ -333,6 +333,7 @@ ${reason}`,
333
333
  }
334
334
  return createFailedToResolveUrlError({
335
335
  reason: `An error occured during specifier resolution`,
336
+ ...detailsFromInjectionsOnOwner(reference),
336
337
  ...detailsFromValueThrown(error),
337
338
  });
338
339
  };
@@ -394,6 +395,7 @@ ${reason}`,
394
395
  return createFailedToFetchUrlContentError({
395
396
  code: "NOT_FOUND",
396
397
  reason: "no entry on filesystem",
398
+ ...detailsFromInjectionsOnOwner(urlInfo.firstReference),
397
399
  });
398
400
  }
399
401
  }
@@ -626,6 +628,32 @@ const getFirstReferenceInProject = (reference) => {
626
628
  return getFirstReferenceInProject(firstReference);
627
629
  };
628
630
 
631
+ // An url written by an injection cannot be resolved: the placeholder is still there
632
+ // when references are analyzed. Rather than guessing what a placeholder looks like
633
+ // (the key is free-form), tell the file it comes from: injections are configured for it.
634
+ const detailsFromInjectionsOnOwner = (reference) => {
635
+ if (!reference) {
636
+ return {};
637
+ }
638
+ const ownerUrlInfo = reference.ownerUrlInfo;
639
+ if (ownerUrlInfo.type !== "html") {
640
+ // "jsenv-ignore" is an html attribute
641
+ return {};
642
+ }
643
+ const { hasInjections } = ownerUrlInfo.context;
644
+ if (!hasInjections || !hasInjections(ownerUrlInfo.url)) {
645
+ return {};
646
+ }
647
+ const { node, attributeName } = reference.astInfo || {};
648
+ if (!node || !attributeName) {
649
+ return {};
650
+ }
651
+ return {
652
+ suggestion: `injections are configured for this file; when "${reference.specifier}" is meant to be written by one of them, add "jsenv-ignore" so jsenv leaves that url alone:
653
+ <${node.nodeName} jsenv-ignore ${attributeName}="${reference.specifier}" />`,
654
+ };
655
+ };
656
+
629
657
  const detailsFromPluginController = (jsenvPluginsController) => {
630
658
  const currentPlugin = jsenvPluginsController.getCurrentPlugin();
631
659
  if (!currentPlugin) {
@@ -2511,7 +2539,14 @@ const injectGlobals = (content, globals, urlInfo) => {
2511
2539
  if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
2512
2540
  return globalsInjectorOnJs(content, globals, urlInfo);
2513
2541
  }
2514
- throw new Error(`cannot inject globals into "${urlInfo.type}"`);
2542
+ throw new Error(
2543
+ createDetailedMessage(`cannot inject globals into "${urlInfo.type}"`, {
2544
+ file: urlInfo.url,
2545
+ ...(urlInfo.isInline
2546
+ ? { "inline content of": urlInfo.inlineUrlSite.url }
2547
+ : {}),
2548
+ }),
2549
+ );
2515
2550
  };
2516
2551
  const globalInjectorOnHtml = (content, globals, urlInfo) => {
2517
2552
  // ideally we would inject an importmap but browser support is too low
@@ -7652,11 +7687,20 @@ const jsenvPluginInjections = (rawAssociations) => {
7652
7687
  { injectionsGetter: rawAssociations },
7653
7688
  context.rootDirectoryUrl,
7654
7689
  );
7655
- const findInjectionsGetter = (urlInfo) => {
7690
+ const findInjectionsGetterForUrl = (url) => {
7656
7691
  const { injectionsGetter } = URL_META.applyAssociations({
7657
- url: asUrlWithoutSearch(urlInfo.url),
7692
+ url: asUrlWithoutSearch(url),
7658
7693
  associations: resolvedAssociations,
7659
7694
  });
7695
+ return injectionsGetter;
7696
+ };
7697
+ // an url written by an injection cannot be resolved during reference analysis;
7698
+ // errors use this to tell the file holds injections and suggest "jsenv-ignore"
7699
+ context.hasInjections = (url) => {
7700
+ return Boolean(findInjectionsGetterForUrl(url));
7701
+ };
7702
+ const findInjectionsGetter = (urlInfo) => {
7703
+ const injectionsGetter = findInjectionsGetterForUrl(urlInfo.url);
7660
7704
  if (injectionsGetter) {
7661
7705
  return { injectionsGetter, isInherited: false };
7662
7706
  }
@@ -7688,9 +7732,7 @@ const jsenvPluginInjections = (rawAssociations) => {
7688
7732
  if (!injections || !isInherited) {
7689
7733
  return injections;
7690
7734
  }
7691
- // the file holds several inline contents; a placeholder configured for the file
7692
- // is expected in one of them, not in each
7693
- return asOptionalInjections(injections);
7735
+ return asInheritedInjections(injections);
7694
7736
  };
7695
7737
  }
7696
7738
  },
@@ -7717,12 +7759,24 @@ const jsenvPluginInjections = (rawAssociations) => {
7717
7759
  };
7718
7760
  };
7719
7761
 
7720
- const asOptionalInjections = (injections) => {
7721
- const optionalInjections = {};
7762
+ // What a file inlines (a <script> or a <style> inside html) is authored in that file
7763
+ // and inherits its injections, with two adjustments:
7764
+ // - a global belongs to the file itself, injecting it into each inline content would
7765
+ // repeat it and reach types that cannot receive globals (css)
7766
+ // - a placeholder configured for the file is expected in one of its inline contents,
7767
+ // not in each, so a missing one is not worth a warning
7768
+ const asInheritedInjections = (injections) => {
7769
+ const inheritedInjections = {};
7722
7770
  for (const key of Object.keys(injections)) {
7723
- optionalInjections[key] = INJECTIONS.optional(injections[key]);
7771
+ const value = injections[key];
7772
+ if (isPlaceholderInjection(value)) {
7773
+ inheritedInjections[key] = INJECTIONS.optional(value);
7774
+ }
7775
+ }
7776
+ if (Object.keys(inheritedInjections).length === 0) {
7777
+ return null;
7724
7778
  }
7725
- return optionalInjections;
7779
+ return inheritedInjections;
7726
7780
  };
7727
7781
 
7728
7782
  /*
@@ -8,7 +8,7 @@
8
8
 
9
9
  <body>
10
10
  <p>Syntax error: <strong>${reasonCode}</strong></p>
11
- <a jsenv-ignore="" href="${errorLinkHref}">${errorLinkText}</a>
11
+ <a href="${errorLinkHref}">${errorLinkText}</a>
12
12
  ${syntaxErrorHTML}
13
13
  </body>
14
14
  </html>
@@ -4563,6 +4563,7 @@ ${reason}`,
4563
4563
  }
4564
4564
  return createFailedToResolveUrlError({
4565
4565
  reason: `An error occured during specifier resolution`,
4566
+ ...detailsFromInjectionsOnOwner(reference),
4566
4567
  ...detailsFromValueThrown(error),
4567
4568
  });
4568
4569
  };
@@ -4624,6 +4625,7 @@ ${reason}`,
4624
4625
  return createFailedToFetchUrlContentError({
4625
4626
  code: "NOT_FOUND",
4626
4627
  reason: "no entry on filesystem",
4628
+ ...detailsFromInjectionsOnOwner(urlInfo.firstReference),
4627
4629
  });
4628
4630
  }
4629
4631
  }
@@ -4856,6 +4858,32 @@ const getFirstReferenceInProject = (reference) => {
4856
4858
  return getFirstReferenceInProject(firstReference);
4857
4859
  };
4858
4860
 
4861
+ // An url written by an injection cannot be resolved: the placeholder is still there
4862
+ // when references are analyzed. Rather than guessing what a placeholder looks like
4863
+ // (the key is free-form), tell the file it comes from: injections are configured for it.
4864
+ const detailsFromInjectionsOnOwner = (reference) => {
4865
+ if (!reference) {
4866
+ return {};
4867
+ }
4868
+ const ownerUrlInfo = reference.ownerUrlInfo;
4869
+ if (ownerUrlInfo.type !== "html") {
4870
+ // "jsenv-ignore" is an html attribute
4871
+ return {};
4872
+ }
4873
+ const { hasInjections } = ownerUrlInfo.context;
4874
+ if (!hasInjections || !hasInjections(ownerUrlInfo.url)) {
4875
+ return {};
4876
+ }
4877
+ const { node, attributeName } = reference.astInfo || {};
4878
+ if (!node || !attributeName) {
4879
+ return {};
4880
+ }
4881
+ return {
4882
+ suggestion: `injections are configured for this file; when "${reference.specifier}" is meant to be written by one of them, add "jsenv-ignore" so jsenv leaves that url alone:
4883
+ <${node.nodeName} jsenv-ignore ${attributeName}="${reference.specifier}" />`,
4884
+ };
4885
+ };
4886
+
4859
4887
  const detailsFromPluginController = (jsenvPluginsController) => {
4860
4888
  const currentPlugin = jsenvPluginsController.getCurrentPlugin();
4861
4889
  if (!currentPlugin) {
@@ -5145,7 +5173,14 @@ const injectGlobals = (content, globals, urlInfo) => {
5145
5173
  if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
5146
5174
  return globalsInjectorOnJs(content, globals, urlInfo);
5147
5175
  }
5148
- throw new Error(`cannot inject globals into "${urlInfo.type}"`);
5176
+ throw new Error(
5177
+ createDetailedMessage(`cannot inject globals into "${urlInfo.type}"`, {
5178
+ file: urlInfo.url,
5179
+ ...(urlInfo.isInline
5180
+ ? { "inline content of": urlInfo.inlineUrlSite.url }
5181
+ : {}),
5182
+ }),
5183
+ );
5149
5184
  };
5150
5185
  const globalInjectorOnHtml = (content, globals, urlInfo) => {
5151
5186
  // ideally we would inject an importmap but browser support is too low
@@ -5211,11 +5246,20 @@ const jsenvPluginInjections = (rawAssociations) => {
5211
5246
  { injectionsGetter: rawAssociations },
5212
5247
  context.rootDirectoryUrl,
5213
5248
  );
5214
- const findInjectionsGetter = (urlInfo) => {
5249
+ const findInjectionsGetterForUrl = (url) => {
5215
5250
  const { injectionsGetter } = URL_META.applyAssociations({
5216
- url: asUrlWithoutSearch(urlInfo.url),
5251
+ url: asUrlWithoutSearch(url),
5217
5252
  associations: resolvedAssociations,
5218
5253
  });
5254
+ return injectionsGetter;
5255
+ };
5256
+ // an url written by an injection cannot be resolved during reference analysis;
5257
+ // errors use this to tell the file holds injections and suggest "jsenv-ignore"
5258
+ context.hasInjections = (url) => {
5259
+ return Boolean(findInjectionsGetterForUrl(url));
5260
+ };
5261
+ const findInjectionsGetter = (urlInfo) => {
5262
+ const injectionsGetter = findInjectionsGetterForUrl(urlInfo.url);
5219
5263
  if (injectionsGetter) {
5220
5264
  return { injectionsGetter, isInherited: false };
5221
5265
  }
@@ -5247,9 +5291,7 @@ const jsenvPluginInjections = (rawAssociations) => {
5247
5291
  if (!injections || !isInherited) {
5248
5292
  return injections;
5249
5293
  }
5250
- // the file holds several inline contents; a placeholder configured for the file
5251
- // is expected in one of them, not in each
5252
- return asOptionalInjections(injections);
5294
+ return asInheritedInjections(injections);
5253
5295
  };
5254
5296
  }
5255
5297
  },
@@ -5276,12 +5318,24 @@ const jsenvPluginInjections = (rawAssociations) => {
5276
5318
  };
5277
5319
  };
5278
5320
 
5279
- const asOptionalInjections = (injections) => {
5280
- const optionalInjections = {};
5321
+ // What a file inlines (a <script> or a <style> inside html) is authored in that file
5322
+ // and inherits its injections, with two adjustments:
5323
+ // - a global belongs to the file itself, injecting it into each inline content would
5324
+ // repeat it and reach types that cannot receive globals (css)
5325
+ // - a placeholder configured for the file is expected in one of its inline contents,
5326
+ // not in each, so a missing one is not worth a warning
5327
+ const asInheritedInjections = (injections) => {
5328
+ const inheritedInjections = {};
5281
5329
  for (const key of Object.keys(injections)) {
5282
- optionalInjections[key] = INJECTIONS.optional(injections[key]);
5330
+ const value = injections[key];
5331
+ if (isPlaceholderInjection(value)) {
5332
+ inheritedInjections[key] = INJECTIONS.optional(value);
5333
+ }
5334
+ }
5335
+ if (Object.keys(inheritedInjections).length === 0) {
5336
+ return null;
5283
5337
  }
5284
- return optionalInjections;
5338
+ return inheritedInjections;
5285
5339
  };
5286
5340
 
5287
5341
  const jsenvPluginInliningAsDataUrl = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "41.4.4",
3
+ "version": "41.4.5",
4
4
  "type": "module",
5
5
  "description": "Tool to develop, test and build js projects",
6
6
  "repository": {
@@ -72,12 +72,12 @@
72
72
  "test:snapshot_clear": "npx @jsenv/filesystem clear **/tests/**/side_effects/"
73
73
  },
74
74
  "dependencies": {
75
- "@jsenv/ast": "6.8.4",
76
- "@jsenv/js-module-fallback": "1.4.37",
75
+ "@jsenv/ast": "6.8.5",
76
+ "@jsenv/js-module-fallback": "1.4.38",
77
77
  "@jsenv/plugin-bundling": "2.10.16",
78
- "@jsenv/plugin-minification": "1.7.5",
79
- "@jsenv/plugin-supervisor": "1.8.8",
80
- "@jsenv/plugin-transpilation": "1.5.78",
78
+ "@jsenv/plugin-minification": "1.7.6",
79
+ "@jsenv/plugin-supervisor": "1.8.9",
80
+ "@jsenv/plugin-transpilation": "1.5.79",
81
81
  "@jsenv/server": "17.6.0",
82
82
  "@jsenv/sourcemap": "1.4.2",
83
83
  "react-table": "7.8.0"
@@ -78,6 +78,7 @@ ${reason}`,
78
78
  }
79
79
  return createFailedToResolveUrlError({
80
80
  reason: `An error occured during specifier resolution`,
81
+ ...detailsFromInjectionsOnOwner(reference),
81
82
  ...detailsFromValueThrown(error),
82
83
  });
83
84
  };
@@ -139,6 +140,7 @@ ${reason}`,
139
140
  return createFailedToFetchUrlContentError({
140
141
  code: "NOT_FOUND",
141
142
  reason: "no entry on filesystem",
143
+ ...detailsFromInjectionsOnOwner(urlInfo.firstReference),
142
144
  });
143
145
  }
144
146
  }
@@ -371,6 +373,32 @@ const getFirstReferenceInProject = (reference) => {
371
373
  return getFirstReferenceInProject(firstReference);
372
374
  };
373
375
 
376
+ // An url written by an injection cannot be resolved: the placeholder is still there
377
+ // when references are analyzed. Rather than guessing what a placeholder looks like
378
+ // (the key is free-form), tell the file it comes from: injections are configured for it.
379
+ const detailsFromInjectionsOnOwner = (reference) => {
380
+ if (!reference) {
381
+ return {};
382
+ }
383
+ const ownerUrlInfo = reference.ownerUrlInfo;
384
+ if (ownerUrlInfo.type !== "html") {
385
+ // "jsenv-ignore" is an html attribute
386
+ return {};
387
+ }
388
+ const { hasInjections } = ownerUrlInfo.context;
389
+ if (!hasInjections || !hasInjections(ownerUrlInfo.url)) {
390
+ return {};
391
+ }
392
+ const { node, attributeName } = reference.astInfo || {};
393
+ if (!node || !attributeName) {
394
+ return {};
395
+ }
396
+ return {
397
+ suggestion: `injections are configured for this file; when "${reference.specifier}" is meant to be written by one of them, add "jsenv-ignore" so jsenv leaves that url alone:
398
+ <${node.nodeName} jsenv-ignore ${attributeName}="${reference.specifier}" />`,
399
+ };
400
+ };
401
+
374
402
  const detailsFromPluginController = (jsenvPluginsController) => {
375
403
  const currentPlugin = jsenvPluginsController.getCurrentPlugin();
376
404
  if (!currentPlugin) {
@@ -1,4 +1,5 @@
1
1
  import { injectJsenvScript, parseHtml, stringifyHtmlAst } from "@jsenv/ast";
2
+ import { createDetailedMessage } from "@jsenv/humanize";
2
3
  import { composeTwoSourcemaps, createMagicSource } from "@jsenv/sourcemap";
3
4
 
4
5
  const injectionSymbol = Symbol.for("jsenv_injection");
@@ -150,7 +151,14 @@ export const injectGlobals = (content, globals, urlInfo) => {
150
151
  if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
151
152
  return globalsInjectorOnJs(content, globals, urlInfo);
152
153
  }
153
- throw new Error(`cannot inject globals into "${urlInfo.type}"`);
154
+ throw new Error(
155
+ createDetailedMessage(`cannot inject globals into "${urlInfo.type}"`, {
156
+ file: urlInfo.url,
157
+ ...(urlInfo.isInline
158
+ ? { "inline content of": urlInfo.inlineUrlSite.url }
159
+ : {}),
160
+ }),
161
+ );
154
162
  };
155
163
  const globalInjectorOnHtml = (content, globals, urlInfo) => {
156
164
  // ideally we would inject an importmap but browser support is too low
@@ -1,6 +1,9 @@
1
1
  import { URL_META } from "@jsenv/url-meta";
2
2
  import { asUrlWithoutSearch, urlToRelativeUrl } from "@jsenv/urls";
3
- import { INJECTIONS } from "../../kitchen/url_graph/url_info_injections.js";
3
+ import {
4
+ INJECTIONS,
5
+ isPlaceholderInjection,
6
+ } from "../../kitchen/url_graph/url_info_injections.js";
4
7
 
5
8
  export const jsenvPluginInjections = (rawAssociations) => {
6
9
  const getDefaultInjections = (urlInfo) => {
@@ -26,11 +29,20 @@ export const jsenvPluginInjections = (rawAssociations) => {
26
29
  { injectionsGetter: rawAssociations },
27
30
  context.rootDirectoryUrl,
28
31
  );
29
- const findInjectionsGetter = (urlInfo) => {
32
+ const findInjectionsGetterForUrl = (url) => {
30
33
  const { injectionsGetter } = URL_META.applyAssociations({
31
- url: asUrlWithoutSearch(urlInfo.url),
34
+ url: asUrlWithoutSearch(url),
32
35
  associations: resolvedAssociations,
33
36
  });
37
+ return injectionsGetter;
38
+ };
39
+ // an url written by an injection cannot be resolved during reference analysis;
40
+ // errors use this to tell the file holds injections and suggest "jsenv-ignore"
41
+ context.hasInjections = (url) => {
42
+ return Boolean(findInjectionsGetterForUrl(url));
43
+ };
44
+ const findInjectionsGetter = (urlInfo) => {
45
+ const injectionsGetter = findInjectionsGetterForUrl(urlInfo.url);
34
46
  if (injectionsGetter) {
35
47
  return { injectionsGetter, isInherited: false };
36
48
  }
@@ -62,9 +74,7 @@ export const jsenvPluginInjections = (rawAssociations) => {
62
74
  if (!injections || !isInherited) {
63
75
  return injections;
64
76
  }
65
- // the file holds several inline contents; a placeholder configured for the file
66
- // is expected in one of them, not in each
67
- return asOptionalInjections(injections);
77
+ return asInheritedInjections(injections);
68
78
  };
69
79
  }
70
80
  },
@@ -91,10 +101,22 @@ export const jsenvPluginInjections = (rawAssociations) => {
91
101
  };
92
102
  };
93
103
 
94
- const asOptionalInjections = (injections) => {
95
- const optionalInjections = {};
104
+ // What a file inlines (a <script> or a <style> inside html) is authored in that file
105
+ // and inherits its injections, with two adjustments:
106
+ // - a global belongs to the file itself, injecting it into each inline content would
107
+ // repeat it and reach types that cannot receive globals (css)
108
+ // - a placeholder configured for the file is expected in one of its inline contents,
109
+ // not in each, so a missing one is not worth a warning
110
+ const asInheritedInjections = (injections) => {
111
+ const inheritedInjections = {};
96
112
  for (const key of Object.keys(injections)) {
97
- optionalInjections[key] = INJECTIONS.optional(injections[key]);
113
+ const value = injections[key];
114
+ if (isPlaceholderInjection(value)) {
115
+ inheritedInjections[key] = INJECTIONS.optional(value);
116
+ }
117
+ }
118
+ if (Object.keys(inheritedInjections).length === 0) {
119
+ return null;
98
120
  }
99
- return optionalInjections;
121
+ return inheritedInjections;
100
122
  };