@jsenv/ast 1.1.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.
@@ -0,0 +1,132 @@
1
+ import { isStringLiteralNode } from "./helpers.js"
2
+
3
+ export const isNewUrlCall = (node) => {
4
+ return (
5
+ node.type === "NewExpression" &&
6
+ node.callee.type === "Identifier" &&
7
+ node.callee.name === "URL"
8
+ )
9
+ }
10
+ export const analyzeNewUrlCall = (node, { isJsModule, onUrl }) => {
11
+ if (node.arguments.length === 1) {
12
+ const firstArgNode = node.arguments[0]
13
+ const urlType = analyzeUrlNodeType(firstArgNode, { isJsModule })
14
+ if (urlType === "StringLiteral") {
15
+ const specifierNode = firstArgNode
16
+ onUrl({
17
+ type: "js_url_specifier",
18
+ subtype: "new_url_first_arg",
19
+ specifier: specifierNode.value,
20
+ specifierStart: specifierNode.start,
21
+ specifierEnd: specifierNode.end,
22
+ specifierLine: specifierNode.loc.start.line,
23
+ specifierColumn: specifierNode.loc.start.column,
24
+ })
25
+ }
26
+ return
27
+ }
28
+ if (node.arguments.length === 2) {
29
+ const firstArgNode = node.arguments[0]
30
+ const secondArgNode = node.arguments[1]
31
+ const baseUrlType = analyzeUrlNodeType(secondArgNode, { isJsModule })
32
+ if (baseUrlType) {
33
+ // we can understand the second argument
34
+ const urlType = analyzeUrlNodeType(firstArgNode, { isJsModule })
35
+ if (urlType === "StringLiteral") {
36
+ // we can understand the first argument
37
+ const specifierNode = firstArgNode
38
+ onUrl({
39
+ type: "js_url_specifier",
40
+ subtype: "new_url_first_arg",
41
+ specifier: specifierNode.value,
42
+ specifierStart: specifierNode.start,
43
+ specifierEnd: specifierNode.end,
44
+ specifierLine: specifierNode.loc.start.line,
45
+ specifierColumn: specifierNode.loc.start.column,
46
+ baseUrlType,
47
+ baseUrl:
48
+ baseUrlType === "StringLiteral" ? secondArgNode.value : undefined,
49
+ })
50
+ }
51
+ if (baseUrlType === "StringLiteral") {
52
+ const specifierNode = secondArgNode
53
+ onUrl({
54
+ type: "js_url_specifier",
55
+ subtype: "new_url_second_arg",
56
+ specifier: specifierNode.value,
57
+ specifierStart: specifierNode.start,
58
+ specifierEnd: specifierNode.end,
59
+ specifierLine: specifierNode.loc.start.line,
60
+ specifierColumn: specifierNode.loc.start.column,
61
+ })
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ const analyzeUrlNodeType = (secondArgNode, { isJsModule }) => {
68
+ if (isStringLiteralNode(secondArgNode)) {
69
+ return "StringLiteral"
70
+ }
71
+ if (isImportMetaUrl(secondArgNode)) {
72
+ return "import.meta.url"
73
+ }
74
+ if (isWindowOrigin(secondArgNode)) {
75
+ return "window.origin"
76
+ }
77
+ if (!isJsModule && isContextMetaUrlFromSystemJs(secondArgNode)) {
78
+ return "context.meta.url"
79
+ }
80
+ if (!isJsModule && isDocumentCurrentScriptSrc(secondArgNode)) {
81
+ return "document.currentScript.src"
82
+ }
83
+ return null
84
+ }
85
+
86
+ const isImportMetaUrl = (node) => {
87
+ return (
88
+ node.type === "MemberExpression" &&
89
+ node.object.type === "MetaProperty" &&
90
+ node.property.type === "Identifier" &&
91
+ node.property.name === "url"
92
+ )
93
+ }
94
+
95
+ const isWindowOrigin = (node) => {
96
+ return (
97
+ node.type === "MemberExpression" &&
98
+ node.object.type === "Identifier" &&
99
+ node.object.name === "window" &&
100
+ node.property.type === "Identifier" &&
101
+ node.property.name === "origin"
102
+ )
103
+ }
104
+
105
+ const isContextMetaUrlFromSystemJs = (node) => {
106
+ return (
107
+ node.type === "MemberExpression" &&
108
+ node.object.type === "MemberExpression" &&
109
+ node.object.object.type === "Identifier" &&
110
+ // because of minification we can't assume _context.
111
+ // so anything matching "*.meta.url" (in the context of new URL())
112
+ // will be assumed to be the equivalent to "import.meta.url"
113
+ // node.object.object.name === "_context" &&
114
+ node.object.property.type === "Identifier" &&
115
+ node.object.property.name === "meta" &&
116
+ node.property.type === "Identifier" &&
117
+ node.property.name === "url"
118
+ )
119
+ }
120
+
121
+ const isDocumentCurrentScriptSrc = (node) => {
122
+ return (
123
+ node.type === "MemberExpression" &&
124
+ node.object.type === "MemberExpression" &&
125
+ node.object.object.type === "Identifier" &&
126
+ node.object.object.name === "document" &&
127
+ node.object.property.type === "Identifier" &&
128
+ node.object.property.name === "currentScript" &&
129
+ node.property.type === "Identifier" &&
130
+ node.property.name === "src"
131
+ )
132
+ }
@@ -0,0 +1,74 @@
1
+ import { isStringLiteralNode } from "./helpers.js"
2
+
3
+ export const isSystemRegisterCall = (node) => {
4
+ const callee = node.callee
5
+ return (
6
+ callee.type === "MemberExpression" &&
7
+ callee.object.type === "Identifier" &&
8
+ callee.object.name === "System" &&
9
+ callee.property.type === "Identifier" &&
10
+ callee.property.name === "register"
11
+ )
12
+ }
13
+ export const analyzeSystemRegisterCall = (node, { onUrl }) => {
14
+ const firstArgNode = node.arguments[0]
15
+ if (firstArgNode.type === "ArrayExpression") {
16
+ analyzeSystemRegisterDeps(firstArgNode, { onUrl })
17
+ return
18
+ }
19
+ if (isStringLiteralNode(firstArgNode)) {
20
+ const secondArgNode = node.arguments[1]
21
+ if (secondArgNode.type === "ArrayExpression") {
22
+ analyzeSystemRegisterDeps(secondArgNode, { onUrl })
23
+ return
24
+ }
25
+ }
26
+ }
27
+ const analyzeSystemRegisterDeps = (node, { onUrl }) => {
28
+ const elements = node.elements
29
+ elements.forEach((element) => {
30
+ if (isStringLiteralNode(element)) {
31
+ const specifierNode = element
32
+ onUrl({
33
+ type: "js_url_specifier",
34
+ subtype: "system_register_arg",
35
+ expectedType: "js_classic",
36
+ specifier: specifierNode.value,
37
+ specifierStart: specifierNode.start,
38
+ specifierEnd: specifierNode.end,
39
+ specifierLine: specifierNode.loc.start.line,
40
+ specifierColumn: specifierNode.loc.start.column,
41
+ })
42
+ }
43
+ })
44
+ }
45
+
46
+ export const isSystemImportCall = (node) => {
47
+ const callee = node.callee
48
+ return (
49
+ callee.type === "MemberExpression" &&
50
+ callee.object.type === "Identifier" &&
51
+ // because of minification we can't assume _context.
52
+ // so anything matching "*.import()"
53
+ // will be assumed to be the equivalent to "import()"
54
+ // callee.object.name === "_context" &&
55
+ callee.property.type === "Identifier" &&
56
+ callee.property.name === "import"
57
+ )
58
+ }
59
+ export const analyzeSystemImportCall = (node, { onUrl }) => {
60
+ const firstArgNode = node.arguments[0]
61
+ if (isStringLiteralNode(firstArgNode)) {
62
+ const specifierNode = firstArgNode
63
+ onUrl({
64
+ type: "js_url_specifier",
65
+ subtype: "system_import_arg",
66
+ expectedType: "js_classic",
67
+ specifier: specifierNode.value,
68
+ specifierStart: specifierNode.start,
69
+ specifierEnd: specifierNode.end,
70
+ specifierLine: specifierNode.loc.start.line,
71
+ specifierColumn: specifierNode.loc.start.column,
72
+ })
73
+ }
74
+ }
@@ -0,0 +1,32 @@
1
+ import { isStringLiteralNode } from "./helpers.js"
2
+
3
+ export const isImportScriptsCall = (node) => {
4
+ const callee = node.callee
5
+ if (callee.type === "Identifier" && callee.name === "importScripts") {
6
+ return true
7
+ }
8
+ return (
9
+ callee.type === "MemberExpression" &&
10
+ callee.object.type === "Identifier" &&
11
+ callee.object.name === "self" &&
12
+ callee.property.type === "Identifier" &&
13
+ callee.property.name === "importScripts"
14
+ )
15
+ }
16
+ export const analyzeImportScriptCalls = (node, { onUrl }) => {
17
+ node.arguments.forEach((arg) => {
18
+ if (isStringLiteralNode(arg)) {
19
+ const specifierNode = arg
20
+ onUrl({
21
+ type: "js_url_specifier",
22
+ subtype: "self_import_scripts_arg",
23
+ expectedType: "js_classic",
24
+ specifier: specifierNode.value,
25
+ specifierStart: specifierNode.start,
26
+ specifierEnd: specifierNode.end,
27
+ specifierLine: specifierNode.loc.start.line,
28
+ specifierColumn: specifierNode.loc.start.column,
29
+ })
30
+ }
31
+ })
32
+ }
@@ -0,0 +1,143 @@
1
+ import { getTypePropertyNode, isStringLiteralNode } from "./helpers.js"
2
+ import { isNewUrlCall, analyzeNewUrlCall } from "./new_url.js"
3
+
4
+ export const isNewWorkerCall = (node) => {
5
+ return (
6
+ node.type === "NewExpression" &&
7
+ node.callee.type === "Identifier" &&
8
+ node.callee.name === "Worker"
9
+ )
10
+ }
11
+ export const analyzeNewWorkerCall = (node, { isJsModule, onUrl }) => {
12
+ analyzeWorkerCallArguments(node, {
13
+ isJsModule,
14
+ onUrl,
15
+ referenceSubtype: "new_worker_first_arg",
16
+ expectedSubtype: "worker",
17
+ })
18
+ }
19
+
20
+ export const isNewSharedWorkerCall = (node) => {
21
+ return (
22
+ node.type === "NewExpression" &&
23
+ node.callee.type === "Identifier" &&
24
+ node.callee.name === "SharedWorker"
25
+ )
26
+ }
27
+ export const analyzeNewSharedWorkerCall = (node, { isJsModule, onUrl }) => {
28
+ analyzeWorkerCallArguments(node, {
29
+ isJsModule,
30
+ onUrl,
31
+ referenceSubtype: "new_shared_worker_first_arg",
32
+ expectedSubtype: "shared_worker",
33
+ })
34
+ }
35
+
36
+ export const isServiceWorkerRegisterCall = (node) => {
37
+ if (node.type !== "CallExpression") {
38
+ return false
39
+ }
40
+ const callee = node.callee
41
+ if (
42
+ callee.type === "MemberExpression" &&
43
+ callee.property.type === "Identifier" &&
44
+ callee.property.name === "register"
45
+ ) {
46
+ const parentObject = callee.object
47
+ if (parentObject.type === "MemberExpression") {
48
+ const parentProperty = parentObject.property
49
+ if (
50
+ parentProperty.type === "Identifier" &&
51
+ parentProperty.name === "serviceWorker"
52
+ ) {
53
+ const grandParentObject = parentObject.object
54
+ if (grandParentObject.type === "MemberExpression") {
55
+ // window.navigator.serviceWorker.register
56
+ const grandParentProperty = grandParentObject.property
57
+ if (
58
+ grandParentProperty.type === "Identifier" &&
59
+ grandParentProperty.name === "navigator"
60
+ ) {
61
+ const ancestorObject = grandParentObject.object
62
+ if (
63
+ ancestorObject.type === "Identifier" &&
64
+ ancestorObject.name === "window"
65
+ ) {
66
+ return true
67
+ }
68
+ }
69
+ }
70
+ if (grandParentObject.type === "Identifier") {
71
+ // navigator.serviceWorker.register
72
+ if (grandParentObject.name === "navigator") {
73
+ return true
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ return false
80
+ }
81
+ export const analyzeServiceWorkerRegisterCall = (
82
+ node,
83
+ { isJsModule, onUrl },
84
+ ) => {
85
+ analyzeWorkerCallArguments(node, {
86
+ isJsModule,
87
+ onUrl,
88
+ referenceSubtype: "service_worker_register_first_arg",
89
+ expectedSubtype: "service_worker",
90
+ })
91
+ }
92
+
93
+ const analyzeWorkerCallArguments = (
94
+ node,
95
+ { isJsModule, onUrl, referenceSubtype, expectedSubtype },
96
+ ) => {
97
+ let expectedType = "js_classic"
98
+ let typePropertyNode
99
+ const secondArgNode = node.arguments[1]
100
+ if (secondArgNode) {
101
+ typePropertyNode = getTypePropertyNode(secondArgNode)
102
+ if (typePropertyNode) {
103
+ const typePropertyValueNode = typePropertyNode.value
104
+ if (isStringLiteralNode(typePropertyValueNode)) {
105
+ const typePropertyValue = typePropertyValueNode.value
106
+ if (typePropertyValue === "module") {
107
+ expectedType = "js_module"
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ const firstArgNode = node.arguments[0]
114
+ if (isStringLiteralNode(firstArgNode)) {
115
+ const specifierNode = firstArgNode
116
+ onUrl({
117
+ type: "js_url_specifier",
118
+ subtype: referenceSubtype,
119
+ expectedType,
120
+ expectedSubtype,
121
+ typePropertyNode,
122
+ specifier: specifierNode.value,
123
+ specifierStart: specifierNode.start,
124
+ specifierEnd: specifierNode.end,
125
+ specifierLine: specifierNode.loc.start.line,
126
+ specifierColumn: specifierNode.loc.start.column,
127
+ })
128
+ return
129
+ }
130
+ if (isNewUrlCall(firstArgNode)) {
131
+ analyzeNewUrlCall(firstArgNode, {
132
+ isJsModule,
133
+ onUrl: (mention) => {
134
+ Object.assign(mention, {
135
+ expectedType,
136
+ expectedSubtype,
137
+ typePropertyNode,
138
+ })
139
+ onUrl(mention)
140
+ },
141
+ })
142
+ }
143
+ }
@@ -0,0 +1,122 @@
1
+ import { ancestor } from "acorn-walk"
2
+
3
+ import { parseJsWithAcorn } from "./parse_js_with_acorn.js"
4
+ import {
5
+ analyzeImportExpression,
6
+ analyzeImportDeclaration,
7
+ analyzeExportNamedDeclaration,
8
+ analyzeExportAllDeclaration,
9
+ } from "./js_static_analysis/import_export.js"
10
+ import {
11
+ isNewWorkerCall,
12
+ analyzeNewWorkerCall,
13
+ isNewSharedWorkerCall,
14
+ analyzeNewSharedWorkerCall,
15
+ isServiceWorkerRegisterCall,
16
+ analyzeServiceWorkerRegisterCall,
17
+ } from "./js_static_analysis/web_worker_entry_point.js"
18
+ import {
19
+ isImportScriptsCall,
20
+ analyzeImportScriptCalls,
21
+ } from "./js_static_analysis/web_worker.js"
22
+ import {
23
+ isNewUrlCall,
24
+ analyzeNewUrlCall,
25
+ } from "./js_static_analysis/new_url.js"
26
+ import {
27
+ isSystemRegisterCall,
28
+ analyzeSystemRegisterCall,
29
+ isSystemImportCall,
30
+ analyzeSystemImportCall,
31
+ } from "./js_static_analysis/system.js"
32
+
33
+ export const parseJsUrls = async ({
34
+ js,
35
+ url,
36
+ isJsModule = false,
37
+ isWebWorker = false,
38
+ } = {}) => {
39
+ const jsUrls = []
40
+ const jsAst = await parseJsWithAcorn({
41
+ js,
42
+ url,
43
+ isJsModule,
44
+ })
45
+ const onUrl = (jsUrl) => {
46
+ jsUrls.push(jsUrl)
47
+ }
48
+ ancestor(jsAst, {
49
+ ImportDeclaration: (node) => {
50
+ analyzeImportDeclaration(node, { onUrl })
51
+ },
52
+ ImportExpression: (node) => {
53
+ analyzeImportExpression(node, { onUrl })
54
+ },
55
+ ExportNamedDeclaration: (node) => {
56
+ analyzeExportNamedDeclaration(node, { onUrl })
57
+ },
58
+ ExportAllDeclaration: (node) => {
59
+ analyzeExportAllDeclaration(node, { onUrl })
60
+ },
61
+ CallExpression: (node) => {
62
+ if (isServiceWorkerRegisterCall(node)) {
63
+ analyzeServiceWorkerRegisterCall(node, {
64
+ isJsModule,
65
+ onUrl,
66
+ })
67
+ return
68
+ }
69
+ if (isWebWorker && isImportScriptsCall(node)) {
70
+ analyzeImportScriptCalls(node, {
71
+ onUrl,
72
+ })
73
+ return
74
+ }
75
+ if (!isJsModule && isSystemRegisterCall(node)) {
76
+ analyzeSystemRegisterCall(node, {
77
+ onUrl,
78
+ })
79
+ return
80
+ }
81
+ if (!isJsModule && isSystemImportCall(node)) {
82
+ analyzeSystemImportCall(node, {
83
+ onUrl,
84
+ })
85
+ return
86
+ }
87
+ },
88
+ NewExpression: (node, ancestors) => {
89
+ if (isNewWorkerCall(node)) {
90
+ analyzeNewWorkerCall(node, {
91
+ isJsModule,
92
+ onUrl,
93
+ })
94
+ return
95
+ }
96
+ if (isNewSharedWorkerCall(node)) {
97
+ analyzeNewSharedWorkerCall(node, {
98
+ isJsModule,
99
+ onUrl,
100
+ })
101
+ return
102
+ }
103
+ if (isNewUrlCall(node)) {
104
+ const parent = ancestors[ancestors.length - 2]
105
+ if (
106
+ parent &&
107
+ (isNewWorkerCall(parent) ||
108
+ isNewSharedWorkerCall(parent) ||
109
+ isServiceWorkerRegisterCall(parent))
110
+ ) {
111
+ return
112
+ }
113
+ analyzeNewUrlCall(node, {
114
+ isJsModule,
115
+ onUrl,
116
+ })
117
+ return
118
+ }
119
+ },
120
+ })
121
+ return jsUrls
122
+ }
@@ -0,0 +1,40 @@
1
+ import { createJsParseError } from "./js_parse_error.js"
2
+
3
+ let AcornParser
4
+ let _getLineInfo
5
+
6
+ export const parseJsWithAcorn = async ({ js, url, isJsModule }) => {
7
+ await initAcornParser()
8
+ try {
9
+ // https://github.com/acornjs/acorn/tree/master/acorn#interface
10
+ const jsAst = AcornParser.parse(js, {
11
+ locations: true,
12
+ allowAwaitOutsideFunction: true,
13
+ sourceType: isJsModule ? "module" : "script",
14
+ ecmaVersion: 2022,
15
+ })
16
+ return jsAst
17
+ } catch (e) {
18
+ if (e && e.name === "SyntaxError") {
19
+ const { line, column } = _getLineInfo(js, e.raisedAt)
20
+ throw createJsParseError({
21
+ message: e.message,
22
+ url,
23
+ line,
24
+ column,
25
+ })
26
+ }
27
+ throw e
28
+ }
29
+ }
30
+
31
+ const initAcornParser = async () => {
32
+ if (AcornParser) {
33
+ return
34
+ }
35
+ const { Parser, getLineInfo } = await import("acorn")
36
+ const { importAssertions } = await import("acorn-import-assertions")
37
+
38
+ AcornParser = Parser.extend(importAssertions)
39
+ _getLineInfo = getLineInfo
40
+ }
package/src/main.js ADDED
@@ -0,0 +1,44 @@
1
+ // html
2
+ export {
3
+ parseHtmlString,
4
+ stringifyHtmlAst,
5
+ parseSvgString,
6
+ stringifySvgAst,
7
+ } from "./html/html_parse.js"
8
+ export { visitHtmlNodes, findHtmlNode } from "./html/html_search.js"
9
+ export { analyzeScriptNode, analyzeLinkNode } from "./html/html_analysis.js"
10
+ export {
11
+ getHtmlNodeText,
12
+ removeHtmlNodeText,
13
+ setHtmlNodeText,
14
+ } from "./html/html_node_text.js"
15
+ export { parseSrcSet, stringifySrcSet } from "./html/html_src_set.js"
16
+ export {
17
+ getHtmlNodeAttribute,
18
+ setHtmlNodeAttributes,
19
+ } from "./html/html_node_attributes.js"
20
+ export {
21
+ removeHtmlNode,
22
+ createHtmlNode,
23
+ injectScriptNodeAsEarlyAsPossible,
24
+ } from "./html/html_node.js"
25
+ export {
26
+ inlineScriptNode,
27
+ inlineLinkStylesheetNode,
28
+ inlineImgNode,
29
+ } from "./html/html_inlining.js"
30
+ export {
31
+ getHtmlNodePosition,
32
+ getHtmlNodeAttributePosition,
33
+ } from "./html/html_node_position.js"
34
+
35
+ // css
36
+ export { applyPostCss } from "./css/apply_post_css.js"
37
+ export { transpileWithParcel, minifyWithParcel } from "./css/parcel_css.js"
38
+ export { postCssPluginUrlVisitor } from "./css/postcss_plugin_url_visitor.js"
39
+
40
+ // js
41
+ export { applyBabelPlugins } from "./js/apply_babel_plugins.js"
42
+ export { injectJsImport } from "./js/babel_utils.js"
43
+ export { parseJsWithAcorn } from "./js/parse_js_with_acorn.js"
44
+ export { parseJsUrls } from "./js/parse_js_urls.js"
package/src/require.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createRequire } from "node:module"
2
+
3
+ export const require = createRequire(import.meta.url)