@jsenv/ast 6.10.3 → 6.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/ast",
3
- "version": "6.10.3",
3
+ "version": "6.10.5",
4
4
  "type": "module",
5
5
  "description": "Parse and transform HTML, CSS and JS",
6
6
  "repository": {
@@ -29,14 +29,14 @@
29
29
  "@babel/core": "8.0.1",
30
30
  "@babel/helper-module-imports": "8.0.0",
31
31
  "@babel/parser": "8.0.4",
32
- "@jsenv/humanize": "1.8.1",
33
- "@jsenv/urls": "2.9.12",
32
+ "@jsenv/humanize": "1.8.2",
33
+ "@jsenv/urls": "2.9.13",
34
34
  "@jsenv/utils": "2.3.1",
35
35
  "acorn": "8.18.0",
36
36
  "acorn-import-attributes": "1.9.5",
37
37
  "acorn-walk": "8.3.5",
38
38
  "parse5": "8.0.1",
39
- "postcss": "8.5.27",
39
+ "postcss": "8.5.28",
40
40
  "postcss-value-parser": "4.2.0"
41
41
  },
42
42
  "devDependencies": {
@@ -10,11 +10,21 @@
10
10
 
11
11
  const PLACEHOLDER = (index) => `var(--jsenv-css-substitution-${index})`;
12
12
 
13
+ // A css comment an author writes in a template that cannot be made readable,
14
+ // to say so and stop being told. Kept as plain css: it costs nothing when the
15
+ // template does become readable, the css pipeline strips it like any comment.
16
+ const OPAQUE_DIRECTIVE = "jsenv-css-opaque";
17
+
18
+ export const hasCssOpaqueDirective = (templateSource) => {
19
+ return templateSource.includes(OPAQUE_DIRECTIVE);
20
+ };
21
+
13
22
  /**
14
23
  * @param {Object} templateLiteralNode a TemplateLiteral acorn node
15
24
  * @param {string} js the source the node comes from
16
- * @returns {{ content: string, substitutions: Array<{placeholder: string, expression: string}> }|null}
17
- * null when at least one expression is not in a css value position
25
+ * @returns {{ content: string, substitutions: Array<{placeholder: string, expression: string}> }
26
+ * | { opaque: { reason: string } }}
27
+ * "opaque" when the css cannot be read: the template is left alone
18
28
  */
19
29
  export const buildCssTemplateSubstitutions = (templateLiteralNode, js) => {
20
30
  const { quasis, expressions } = templateLiteralNode;
@@ -22,8 +32,12 @@ export const buildCssTemplateSubstitutions = (templateLiteralNode, js) => {
22
32
  for (const quasi of quasis) {
23
33
  const { cooked } = quasi.value;
24
34
  if (cooked === undefined) {
25
- // an escape sequence acorn could not cook: what the css is cannot be told
26
- return null;
35
+ return {
36
+ opaque: {
37
+ reason:
38
+ "an escape sequence cannot be cooked into the css it stands for",
39
+ },
40
+ };
27
41
  }
28
42
  parts.push(cooked);
29
43
  }
@@ -39,9 +53,14 @@ export const buildCssTemplateSubstitutions = (templateLiteralNode, js) => {
39
53
  });
40
54
  content += placeholder + parts[i + 1];
41
55
  }
42
- for (const position of positions) {
43
- if (!isCssValuePosition(content, position)) {
44
- return null;
56
+ for (let i = 0; i < positions.length; i++) {
57
+ const nonValuePosition = readNonValuePosition(content, positions[i]);
58
+ if (nonValuePosition) {
59
+ return {
60
+ opaque: {
61
+ reason: `"\${${substitutions[i].expression}}" stands in ${nonValuePosition}`,
62
+ },
63
+ };
45
64
  }
46
65
  }
47
66
  return { content, substitutions };
@@ -49,7 +68,8 @@ export const buildCssTemplateSubstitutions = (templateLiteralNode, js) => {
49
68
 
50
69
  // Reads the css from its start up to "position", keeping just enough state to
51
70
  // tell what the placeholder sitting there would be part of.
52
- const isCssValuePosition = (css, position) => {
71
+ // Returns what it is part of when that is not a value, null when it is one.
72
+ const readNonValuePosition = (css, position) => {
53
73
  let braceDepth = 0;
54
74
  let stringQuote = null;
55
75
  let inComment = false;
@@ -130,29 +150,32 @@ const isCssValuePosition = (css, position) => {
130
150
  }
131
151
  i++;
132
152
  }
133
- if (inComment || stringQuote) {
134
- return false;
153
+ if (inComment) {
154
+ return "a comment";
155
+ }
156
+ if (stringQuote) {
157
+ return "a string";
135
158
  }
136
159
  if (braceDepth === 0) {
137
- // outside any rule: a selector, or an at-rule prelude
138
- return false;
160
+ return "a selector, or an at-rule prelude";
139
161
  }
140
162
  if (!colonSeen) {
141
- // a property name, or a nested selector
142
- return false;
163
+ return "a property name, or a nested selector";
143
164
  }
144
165
  if (css.slice(statementStart, position).trimStart().startsWith("@")) {
145
- // an at-rule prelude nested in a rule, "@media (min-width: ...)"
146
- return false;
166
+ return "a nested at-rule prelude";
147
167
  }
148
168
  if (functionStack[functionStack.length - 1] === "url") {
149
169
  // url() takes an url, and an url built at runtime is not one the build
150
170
  // can follow to a file
151
- return false;
171
+ return "url()";
152
172
  }
153
173
  // A ":" also opens a pseudo class, so what looks like a value here can still
154
174
  // be the end of a nested selector; that is settled by what closes it.
155
- return closedByDeclarationEnd(css, position);
175
+ if (!closedByDeclarationEnd(css, position)) {
176
+ return "a nested selector";
177
+ }
178
+ return null;
156
179
  };
157
180
 
158
181
  const readFunctionNameBefore = (css, parenthesisIndex) => {
@@ -12,7 +12,7 @@ export const isImportMetaCssAssignment = (node) => {
12
12
 
13
13
  export const analyzeImportMetaCssAssignment = (
14
14
  node,
15
- { js, ast, ancestors, onInlineContent },
15
+ { js, ast, ancestors, onInlineContent, onOpaqueContent },
16
16
  ) => {
17
17
  // "import.meta.css = [css, url]": the array form a pre-built file ships
18
18
  // (see jsenv:import_meta_css); the css is the first element.
@@ -21,6 +21,9 @@ export const analyzeImportMetaCssAssignment = (
21
21
  if (!assignedNode) {
22
22
  return;
23
23
  }
24
+ // a pre-built package ships "[css, url]": its css was written in a source
25
+ // this build does not have, so this build is not where it can be fixed
26
+ const alreadyBuilt = node.right.type === "ArrayExpression";
24
27
  // "const css = `...`; import.meta.css = css;" — the css is where the constant
25
28
  // is declared, and that is where it is read and rewritten.
26
29
  const nodeHoldingContent =
@@ -58,7 +61,16 @@ export const analyzeImportMetaCssAssignment = (
58
61
  nodeHoldingContent,
59
62
  js,
60
63
  );
61
- if (!substitutionInfo) {
64
+ if (substitutionInfo.opaque) {
65
+ onOpaqueContent({
66
+ ...position,
67
+ ...substitutionInfo.opaque,
68
+ alreadyBuilt,
69
+ templateSource: js.slice(
70
+ nodeHoldingContent.start,
71
+ nodeHoldingContent.end,
72
+ ),
73
+ });
62
74
  return;
63
75
  }
64
76
  onInlineContent({
@@ -69,6 +81,7 @@ export const analyzeImportMetaCssAssignment = (
69
81
  quote: "`",
70
82
  content: substitutionInfo.content,
71
83
  substitutions: substitutionInfo.substitutions,
84
+ alreadyBuilt,
72
85
  astInfo: { node: nodeHoldingContent },
73
86
  });
74
87
  };
@@ -78,6 +78,13 @@ export const parseJsUrls = ({
78
78
  ...inlineContentInfo,
79
79
  });
80
80
  };
81
+ // css the build cannot read: no url info comes out of it, only a way to tell
82
+ const onOpaqueContent = (opaqueContentInfo) => {
83
+ jsUrls.push({
84
+ isOpaqueCss: true,
85
+ ...opaqueContentInfo,
86
+ });
87
+ };
81
88
 
82
89
  const getCommentBeforeClosingParenthesis = (
83
90
  // either new InlineContent() or JSON.parse() for instance
@@ -141,6 +148,7 @@ export const parseJsUrls = ({
141
148
  ast,
142
149
  ancestors,
143
150
  onInlineContent,
151
+ onOpaqueContent,
144
152
  });
145
153
  }
146
154
  },
package/src/main.js CHANGED
@@ -59,6 +59,9 @@ export { visitJsAstUntil } from "./js/visit_js_ast_until.js";
59
59
  export { visitJsAst } from "./js/visit_js_ast.js";
60
60
  export { getImportMetaPropertyName } from "./js/import_meta.js";
61
61
  export { getUrlForContentInsideJs } from "./js/js_inline_content_url.js";
62
- export { renderCssTemplateLiteral } from "./js/js_static_analysis/css_template_substitutions.js";
62
+ export {
63
+ hasCssOpaqueDirective,
64
+ renderCssTemplateLiteral,
65
+ } from "./js/js_static_analysis/css_template_substitutions.js";
63
66
 
64
67
  export { generateUrlForInlineContent } from "./inline_content_url.js";