@jsenv/ast 4.0.3 → 5.0.0

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": "4.0.3",
3
+ "version": "5.0.0",
4
4
  "description": "Parse and transform HTML,CSS and JS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,12 +33,12 @@
33
33
  "test": "node --conditions=development ./scripts/test.mjs"
34
34
  },
35
35
  "dependencies": {
36
- "@babel/parser": "7.22.4",
37
- "acorn": "8.8.2",
36
+ "@babel/parser": "7.22.7",
37
+ "acorn": "8.10.0",
38
38
  "acorn-import-assertions": "1.9.0",
39
39
  "acorn-walk": "8.2.0",
40
40
  "parse5": "7.1.2",
41
- "postcss": "8.4.24",
41
+ "postcss": "8.4.25",
42
42
  "postcss-value-parser": "4.2.0"
43
43
  }
44
44
  }
@@ -1,6 +1,9 @@
1
1
  import { parseFragment } from "parse5";
2
2
 
3
- import { setHtmlNodeAttributes } from "./html_node_attributes.js";
3
+ import {
4
+ getHtmlNodeAttribute,
5
+ setHtmlNodeAttributes,
6
+ } from "./html_node_attributes.js";
4
7
  import { findHtmlNode } from "./html_search.js";
5
8
  import { analyzeScriptNode } from "./html_analysis.js";
6
9
  import {
@@ -56,9 +59,43 @@ export const injectHtmlNodeAsEarlyAsPossible = (
56
59
  "jsenv-injected-by": jsenvPluginName,
57
60
  });
58
61
  const isScript = node.nodeName === "script";
59
-
60
62
  if (isScript) {
61
- const isJsModule = analyzeScriptNode(node).type === "js_module";
63
+ const { type } = analyzeScriptNode(node);
64
+ const headNode = findChild(htmlAst, (node) => node.nodeName === "html")
65
+ .childNodes[0];
66
+
67
+ // <script type="importmap">
68
+ // - after any <link>
69
+ // - but before first <link rel="modulepreload">
70
+ // - and before <script type="module">
71
+ const isImportmap = type === "importmap";
72
+ if (isImportmap) {
73
+ let after = headNode.childNodes[0];
74
+ for (const child of headNode.childNodes) {
75
+ if (child.nodeName === "link") {
76
+ if (getHtmlNodeAttribute(child, "rel") === "modulepreload") {
77
+ return insertHtmlNodeBefore(node, child);
78
+ }
79
+ after = child;
80
+ continue;
81
+ }
82
+ if (
83
+ child.nodeName === "script" &&
84
+ analyzeScriptNode(child).type === "module"
85
+ ) {
86
+ return insertHtmlNodeBefore(node, child);
87
+ }
88
+ }
89
+ if (after) {
90
+ return insertHtmlNodeAfter(node, after);
91
+ }
92
+ return injectHtmlNode(htmlAst, node);
93
+ }
94
+ // <script type="module">
95
+ // - after <script type="importmap">
96
+ // - and after any <link>
97
+ // - and before first <script type="module">
98
+ const isJsModule = type === "js_module";
62
99
  if (isJsModule) {
63
100
  const firstImportmapScript = findHtmlNode(htmlAst, (node) => {
64
101
  return (
@@ -66,47 +103,46 @@ export const injectHtmlNodeAsEarlyAsPossible = (
66
103
  analyzeScriptNode(node).type === "importmap"
67
104
  );
68
105
  });
69
-
70
106
  if (firstImportmapScript) {
71
- const importmapParent = firstImportmapScript.parentNode;
72
- const importmapSiblings = importmapParent.childNodes;
73
- const nextSiblings = importmapSiblings.slice(
74
- importmapSiblings.indexOf(firstImportmapScript) + 1,
75
- );
76
- let after = firstImportmapScript;
77
- for (const nextSibling of nextSiblings) {
78
- if (nextSibling.nodeName === "script") {
79
- insertHtmlNodeBefore(node, nextSibling);
80
- return;
81
- }
82
- if (nextSibling.nodeName === "link") {
83
- after = nextSibling;
84
- }
107
+ return insertHtmlNodeAfter(node, firstImportmapScript);
108
+ }
109
+ let after = headNode.childNodes[0];
110
+ for (const child of headNode.childNodes) {
111
+ if (child.nodeName === "link") {
112
+ after = child;
113
+ continue;
85
114
  }
86
- insertHtmlNodeAfter(node, after);
87
- return;
115
+ if (
116
+ child.nodeName === "script" &&
117
+ analyzeScriptNode(child).type === "module"
118
+ ) {
119
+ return insertHtmlNodeBefore(node, child);
120
+ }
121
+ }
122
+ if (after) {
123
+ return insertHtmlNodeAfter(node, after);
88
124
  }
125
+ return injectHtmlNode(htmlAst, node);
89
126
  }
90
- const headNode = findChild(htmlAst, (node) => node.nodeName === "html")
91
- .childNodes[0];
127
+ // <script> or <script type="text/jsx">, ...
128
+ // - after any <link>
129
+ // - before any <script>
92
130
  let after = headNode.childNodes[0];
93
131
  for (const child of headNode.childNodes) {
94
- if (child.nodeName === "script") {
95
- insertHtmlNodeBefore(node, child);
96
- return;
97
- }
98
132
  if (child.nodeName === "link") {
99
133
  after = child;
134
+ continue;
135
+ }
136
+ if (child.nodeName === "script") {
137
+ return insertHtmlNodeBefore(node, child);
100
138
  }
101
139
  }
102
140
  if (after) {
103
- insertHtmlNodeAfter(node, after);
141
+ return insertHtmlNodeAfter(node, after);
104
142
  }
105
- return;
143
+ return injectHtmlNode(htmlAst, node);
106
144
  }
107
-
108
- injectHtmlNode(htmlAst, node);
109
- return;
145
+ return injectHtmlNode(htmlAst, node);
110
146
  };
111
147
 
112
148
  export const insertHtmlNodeBefore = (nodeToInsert, futureNextSibling) => {
@@ -3,7 +3,7 @@ import { isStringLiteralNode } from "./helpers.js";
3
3
  export const analyzeImportDeclaration = (node, { onUrl }) => {
4
4
  const specifierNode = node.source;
5
5
  const assertionInfo = extractImportAssertionsInfo(node);
6
- onUrl({
6
+ const info = {
7
7
  type: "js_import",
8
8
  subtype: "import_static",
9
9
  specifier: specifierNode.value,
@@ -11,9 +11,17 @@ export const analyzeImportDeclaration = (node, { onUrl }) => {
11
11
  end: specifierNode.end,
12
12
  line: specifierNode.loc.start.line,
13
13
  column: specifierNode.loc.start.column,
14
- expectedType: assertionInfo ? assertionInfo.assert.type : "js_module",
15
- ...assertionInfo,
16
- });
14
+ expectedType: "js_module",
15
+ astInfo: { node: specifierNode },
16
+ };
17
+ if (assertionInfo) {
18
+ const { importAttributes, importNode, importTypeAttributeNode } =
19
+ assertionInfo;
20
+ info.expectedType = importAttributes.type;
21
+ info.importAttributes = importAttributes;
22
+ Object.assign(info.astInfo, { importNode, importTypeAttributeNode });
23
+ }
24
+ onUrl(info);
17
25
  };
18
26
  export const analyzeImportExpression = (node, { onUrl }) => {
19
27
  const specifierNode = node.source;
@@ -21,18 +29,25 @@ export const analyzeImportExpression = (node, { onUrl }) => {
21
29
  return;
22
30
  }
23
31
  const assertionInfo = extractImportAssertionsInfo(node);
24
-
25
- onUrl({
32
+ const info = {
26
33
  type: "js_import",
27
34
  subtype: "import_dynamic",
35
+ expectedType: "js_module",
28
36
  specifier: specifierNode.value,
29
37
  start: specifierNode.start,
30
38
  end: specifierNode.end,
31
39
  line: specifierNode.loc.start.line,
32
40
  column: specifierNode.loc.start.column,
33
- expectedType: assertionInfo ? assertionInfo.assert.type : "js_module",
34
- ...assertionInfo,
35
- });
41
+ astInfo: { node: specifierNode },
42
+ };
43
+ if (assertionInfo) {
44
+ const { importAttributes, importNode, importTypeAttributeNode } =
45
+ assertionInfo;
46
+ info.expectedType = importAttributes.type;
47
+ info.importAttributes = importAttributes;
48
+ Object.assign(info.astInfo, { importNode, importTypeAttributeNode });
49
+ }
50
+ onUrl(info);
36
51
  };
37
52
  export const analyzeExportNamedDeclaration = (node, { onUrl }) => {
38
53
  const specifierNode = node.source;
@@ -52,6 +67,7 @@ export const analyzeExportNamedDeclaration = (node, { onUrl }) => {
52
67
  end: specifierNode.end,
53
68
  line: specifierNode.loc.start.line,
54
69
  column: specifierNode.loc.start.column,
70
+ astInfo: { node: specifierNode },
55
71
  });
56
72
  };
57
73
  export const analyzeExportAllDeclaration = (node, { onUrl }) => {
@@ -64,6 +80,7 @@ export const analyzeExportAllDeclaration = (node, { onUrl }) => {
64
80
  end: specifierNode.end,
65
81
  line: specifierNode.loc.start.line,
66
82
  column: specifierNode.loc.start.column,
83
+ astInfo: { node: specifierNode },
67
84
  });
68
85
  };
69
86
 
@@ -88,10 +105,11 @@ const extractImportAssertionsInfo = (node) => {
88
105
  return null;
89
106
  }
90
107
  return {
91
- assertNode: typeAssertionNode,
92
- assert: {
108
+ importAttributes: {
93
109
  type: typeNode.value,
94
110
  },
111
+ importNode: node,
112
+ importTypeAttributeNode: typeAssertionNode,
95
113
  };
96
114
  }
97
115
  // dynamic import
@@ -127,9 +145,10 @@ const extractImportAssertionsInfo = (node) => {
127
145
  return null;
128
146
  }
129
147
  return {
130
- assertNode: firstArgNode,
131
- assert: {
148
+ importAttributes: {
132
149
  type: typePropertyValue.value,
133
150
  },
151
+ importNode: node,
152
+ importTypeAttributeNode: typePropertyNode,
134
153
  };
135
154
  };
@@ -14,7 +14,6 @@ export const analyzeImportMetaResolveCall = (node, { onUrl }) => {
14
14
  const firstArg = node.arguments[0];
15
15
  if (firstArg && isStringLiteralNode(firstArg)) {
16
16
  onUrl({
17
- node,
18
17
  type: "js_import",
19
18
  subtype: "import_meta_resolve",
20
19
  specifier: firstArg.value,
@@ -22,6 +21,7 @@ export const analyzeImportMetaResolveCall = (node, { onUrl }) => {
22
21
  end: firstArg.end,
23
22
  line: firstArg.loc.start.line,
24
23
  column: firstArg.loc.start.column,
24
+ astInfo: { node },
25
25
  });
26
26
  }
27
27
  };
@@ -16,7 +16,6 @@ export const analyzeJSONParseCall = (node, { onInlineContent }) => {
16
16
  const contentInfo = extractContentInfo(nodeHoldingContent);
17
17
  if (contentInfo) {
18
18
  onInlineContent({
19
- node: nodeHoldingContent,
20
19
  type: "json_parse_first_arg",
21
20
  contentType: "application/json",
22
21
  start: nodeHoldingContent.start,
@@ -28,6 +27,7 @@ export const analyzeJSONParseCall = (node, { onInlineContent }) => {
28
27
  nodeType: contentInfo.nodeType,
29
28
  quote: contentInfo.quote,
30
29
  content: contentInfo.content,
30
+ astInfo: { node: nodeHoldingContent },
31
31
  });
32
32
  }
33
33
  };
@@ -38,7 +38,6 @@ export const analyzeNewBlobCall = (node, { onInlineContent }) => {
38
38
  const contentInfo = extractContentInfo(nodeHoldingContent);
39
39
  if (contentInfo) {
40
40
  onInlineContent({
41
- node: nodeHoldingContent,
42
41
  type: "new_blob_first_arg",
43
42
  contentType,
44
43
  start: nodeHoldingContent.start,
@@ -50,6 +49,7 @@ export const analyzeNewBlobCall = (node, { onInlineContent }) => {
50
49
  nodeType: contentInfo.nodeType,
51
50
  quote: contentInfo.quote,
52
51
  content: contentInfo.content,
52
+ astInfo: { node: nodeHoldingContent },
53
53
  });
54
54
  }
55
55
  };
@@ -32,7 +32,6 @@ export const analyzeNewInlineContentCall = (node, { onInlineContent }) => {
32
32
  const contentInfo = extractContentInfo(nodeHoldingContent);
33
33
  if (contentInfo) {
34
34
  onInlineContent({
35
- node: nodeHoldingContent,
36
35
  type: "new_inline_content_first_arg",
37
36
  contentType,
38
37
  start: nodeHoldingContent.start,
@@ -44,6 +43,7 @@ export const analyzeNewInlineContentCall = (node, { onInlineContent }) => {
44
43
  nodeType: contentInfo.nodeType,
45
44
  quote: contentInfo.quote,
46
45
  content: contentInfo.content,
46
+ astInfo: { node: nodeHoldingContent },
47
47
  });
48
48
  }
49
49
  };
@@ -21,6 +21,7 @@ export const analyzeNewUrlCall = (node, { isJsModule, onUrl }) => {
21
21
  end: specifierNode.end,
22
22
  line: specifierNode.loc.start.line,
23
23
  column: specifierNode.loc.start.column,
24
+ astInfo: { node: specifierNode },
24
25
  });
25
26
  }
26
27
  return;
@@ -53,6 +54,7 @@ export const analyzeNewUrlCall = (node, { isJsModule, onUrl }) => {
53
54
  baseUrlType,
54
55
  baseUrl:
55
56
  baseUrlType === "StringLiteral" ? secondArgNode.value : undefined,
57
+ astInfo: { node: firstArgNode },
56
58
  });
57
59
  }
58
60
  if (baseUrlType === "StringLiteral") {
@@ -65,6 +67,7 @@ export const analyzeNewUrlCall = (node, { isJsModule, onUrl }) => {
65
67
  end: specifierNode.end,
66
68
  line: specifierNode.loc.start.line,
67
69
  column: specifierNode.loc.start.column,
70
+ astInfo: { node: specifierNode },
68
71
  });
69
72
  }
70
73
  }
@@ -38,6 +38,7 @@ const analyzeSystemRegisterDeps = (node, { onUrl }) => {
38
38
  end: specifierNode.end,
39
39
  line: specifierNode.loc.start.line,
40
40
  column: specifierNode.loc.start.column,
41
+ astInfo: { node: specifierNode },
41
42
  });
42
43
  }
43
44
  });
@@ -69,6 +70,7 @@ export const analyzeSystemImportCall = (node, { onUrl }) => {
69
70
  end: specifierNode.end,
70
71
  line: specifierNode.loc.start.line,
71
72
  column: specifierNode.loc.start.column,
73
+ astInfo: { node: specifierNode },
72
74
  });
73
75
  }
74
76
  };
@@ -101,6 +103,7 @@ export const analyzeSystemResolveCall = (node, { onUrl }) => {
101
103
  end: specifierNode.end,
102
104
  line: specifierNode.loc.start.line,
103
105
  column: specifierNode.loc.start.column,
106
+ astInfo: { node: specifierNode },
104
107
  });
105
108
  }
106
109
  };
@@ -26,6 +26,7 @@ export const analyzeImportScriptCalls = (node, { onUrl }) => {
26
26
  end: specifierNode.end,
27
27
  line: specifierNode.loc.start.line,
28
28
  column: specifierNode.loc.start.column,
29
+ astInfo: { node: specifierNode },
29
30
  });
30
31
  }
31
32
  });
@@ -122,38 +122,41 @@ const analyzeWorkerCallArguments = (
122
122
  subtype: referenceSubtype,
123
123
  expectedType,
124
124
  expectedSubtype,
125
- typePropertyNode,
126
125
  specifier: specifierNode.value,
127
126
  start: specifierNode.start,
128
127
  end: specifierNode.end,
129
128
  line: specifierNode.loc.start.line,
130
129
  column: specifierNode.loc.start.column,
130
+ astInfo: {
131
+ node,
132
+ typePropertyNode,
133
+ },
131
134
  });
132
135
  return;
133
136
  }
134
137
  if (isNewUrlCall(firstArgNode)) {
135
138
  analyzeNewUrlCall(firstArgNode, {
136
139
  isJsModule,
137
- onUrl: (mention) => {
138
- Object.assign(mention, {
140
+ onUrl: (info) => {
141
+ info.astInfo.typePropertyNode = typePropertyNode;
142
+ Object.assign(info, {
139
143
  expectedType,
140
144
  expectedSubtype,
141
- typePropertyNode,
142
145
  });
143
- onUrl(mention);
146
+ onUrl(info);
144
147
  },
145
148
  });
146
149
  return;
147
150
  }
148
151
  if (isJsModule && isImportMetaResolveCall(firstArgNode)) {
149
152
  analyzeImportMetaResolveCall(firstArgNode, {
150
- onUrl: (mention) => {
151
- Object.assign(mention, {
153
+ onUrl: (info) => {
154
+ info.astInfo.typePropertyNode = typePropertyNode;
155
+ Object.assign(info, {
152
156
  expectedType,
153
157
  expectedSubtype,
154
- typePropertyNode,
155
158
  });
156
- onUrl(mention);
159
+ onUrl(info);
157
160
  },
158
161
  });
159
162
  return;
@@ -48,19 +48,22 @@ import {
48
48
  analyzeJSONParseCall,
49
49
  } from "./js_static_analysis/json_parse.js";
50
50
 
51
- export const parseJsUrls = async ({
51
+ export const parseJsUrls = ({
52
52
  js,
53
53
  url,
54
+ ast,
54
55
  isJsModule = false,
55
56
  isWebWorker = false,
56
57
  inlineContent = true,
57
58
  } = {}) => {
58
59
  const jsUrls = [];
59
- const jsAst = await parseJsWithAcorn({
60
- js,
61
- url,
62
- isJsModule,
63
- });
60
+ if (ast === undefined) {
61
+ ast = parseJsWithAcorn({
62
+ js,
63
+ url,
64
+ isJsModule,
65
+ });
66
+ }
64
67
  const onUrl = (jsUrl) => {
65
68
  jsUrls.push(jsUrl);
66
69
  };
@@ -70,7 +73,7 @@ export const parseJsUrls = async ({
70
73
  ...inlineContentInfo,
71
74
  });
72
75
  };
73
- ancestor(jsAst, {
76
+ ancestor(ast, {
74
77
  ImportDeclaration: (node) => {
75
78
  analyzeImportDeclaration(node, { onUrl });
76
79
  },
@@ -1,10 +1,11 @@
1
+ import { Parser, getLineInfo } from "acorn";
2
+ import { importAssertions } from "acorn-import-assertions";
3
+
1
4
  import { createJsParseError } from "./js_parse_error.js";
2
5
 
3
- let AcornParser;
4
- let _getLineInfo;
6
+ export const parseJsWithAcorn = ({ js, url, isJsModule }) => {
7
+ const AcornParser = Parser.extend(importAssertions);
5
8
 
6
- export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
7
- await initAcornParser();
8
9
  try {
9
10
  // https://github.com/acornjs/acorn/tree/master/acorn#interface
10
11
  const jsAst = AcornParser.parse(js, {
@@ -16,10 +17,11 @@ export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
16
17
  return jsAst;
17
18
  } catch (e) {
18
19
  if (e && e.name === "SyntaxError") {
19
- const { line, column } = _getLineInfo(js, e.raisedAt);
20
+ const { line, column } = getLineInfo(js, e.raisedAt);
20
21
  throw createJsParseError({
21
22
  message: e.message,
22
23
  reasonCode: e.message,
24
+ content: js,
23
25
  url,
24
26
  line,
25
27
  column,
@@ -28,14 +30,3 @@ export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
28
30
  throw e;
29
31
  }
30
32
  };
31
-
32
- const initAcornParser = async () => {
33
- if (AcornParser) {
34
- return;
35
- }
36
- const { Parser, getLineInfo } = await import("acorn");
37
- const { importAssertions } = await import("acorn-import-assertions");
38
-
39
- AcornParser = Parser.extend(importAssertions);
40
- _getLineInfo = getLineInfo;
41
- };
@@ -0,0 +1,30 @@
1
+ import { simple } from "acorn-walk";
2
+
3
+ const stop = {};
4
+
5
+ export const visitJsAstUntil = (ast, visitors) => {
6
+ const visitorsMapped = {};
7
+ let returnValue = null;
8
+ Object.keys(visitors).forEach((key) => {
9
+ visitorsMapped[key] = (node, ...args) => {
10
+ const visitorReturnValue = visitors[key](node, ...args);
11
+ if (visitorReturnValue === true) {
12
+ returnValue = node;
13
+ throw stop;
14
+ }
15
+ if (typeof visitorReturnValue === "object" && returnValue !== null) {
16
+ returnValue = visitorReturnValue;
17
+ throw stop;
18
+ }
19
+ };
20
+ });
21
+ try {
22
+ simple(ast, visitorsMapped);
23
+ return returnValue;
24
+ } catch (e) {
25
+ if (e === stop) {
26
+ return returnValue;
27
+ }
28
+ throw e;
29
+ }
30
+ };
package/src/main.js CHANGED
@@ -42,3 +42,4 @@ export { applyBabelPlugins } from "./js/apply_babel_plugins.js";
42
42
  export { injectJsImport } from "./js/babel_utils.js";
43
43
  export { parseJsWithAcorn } from "./js/parse_js_with_acorn.js";
44
44
  export { parseJsUrls } from "./js/parse_js_urls.js";
45
+ export { visitJsAstUntil } from "./js/visit_js_ast_until.js";