@jsenv/ast 3.0.6 → 3.0.8

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.
@@ -1,32 +1,118 @@
1
+ import {
2
+ getHtmlNodeAttribute,
3
+ setHtmlNodeAttributes,
4
+ } from "./html_node_attributes.js";
5
+
1
6
  export const getHtmlNodeText = (htmlNode) => {
2
- const textNode = getTextNode(htmlNode)
3
- return textNode ? textNode.value : undefined
4
- }
7
+ const textNode = getTextNode(htmlNode);
8
+ return textNode ? textNode.value : undefined;
9
+ };
5
10
 
6
11
  const getTextNode = (htmlNode) => {
7
- const firstChild = htmlNode.childNodes[0]
12
+ if (htmlNode.nodeName === "#text") {
13
+ return null;
14
+ }
15
+ const firstChild = htmlNode.childNodes[0];
8
16
  const textNode =
9
- firstChild && firstChild.nodeName === "#text" ? firstChild : null
10
- return textNode
11
- }
17
+ firstChild && firstChild.nodeName === "#text" ? firstChild : null;
18
+ return textNode;
19
+ };
12
20
 
13
21
  export const removeHtmlNodeText = (htmlNode) => {
14
- const textNode = getTextNode(htmlNode)
22
+ const textNode = getTextNode(htmlNode);
15
23
  if (textNode) {
16
- htmlNode.childNodes = []
24
+ htmlNode.childNodes = [];
17
25
  }
18
- }
26
+ };
19
27
 
20
- export const setHtmlNodeText = (htmlNode, textContent) => {
21
- const textNode = getTextNode(htmlNode)
28
+ export const setHtmlNodeText = (
29
+ htmlNode,
30
+ textContent,
31
+ { indentation } = {},
32
+ ) => {
33
+ if (indentation === "auto") {
34
+ const contentIndentedAttribute = getHtmlNodeAttribute(
35
+ htmlNode,
36
+ "content-indented",
37
+ );
38
+ if (contentIndentedAttribute === undefined) {
39
+ const indentation = getIndentation(htmlNode);
40
+ textContent = setIndentation(textContent, indentation);
41
+ setHtmlNodeAttributes(htmlNode, {
42
+ "content-indented": "",
43
+ });
44
+ }
45
+ }
46
+ const textNode = getTextNode(htmlNode);
22
47
  if (textNode) {
23
- textNode.value = textContent
48
+ textNode.value = textContent;
24
49
  } else {
25
50
  const newTextNode = {
26
51
  nodeName: "#text",
27
52
  value: textContent,
28
53
  parentNode: htmlNode,
54
+ };
55
+ htmlNode.childNodes.splice(0, 0, newTextNode);
56
+ }
57
+ };
58
+
59
+ export const getIndentation = (htmlNode) => {
60
+ const parentNode = htmlNode.parentNode;
61
+ if (!parentNode) {
62
+ return "";
63
+ }
64
+
65
+ const siblings = parentNode.childNodes;
66
+ const index = siblings.indexOf(htmlNode);
67
+ if (index === 0) {
68
+ if (htmlNode.nodeName === "#text") {
69
+ return extractIndentation(htmlNode);
70
+ }
71
+ return "";
72
+ }
73
+
74
+ let textNodeIndex = 0;
75
+ let textNode;
76
+ while (textNodeIndex !== index) {
77
+ const previousSibling = siblings[textNodeIndex];
78
+ textNodeIndex++;
79
+ if (previousSibling.nodeName === "#text") {
80
+ textNode = previousSibling;
81
+ break;
29
82
  }
30
- htmlNode.childNodes.splice(0, 0, newTextNode)
31
83
  }
32
- }
84
+ if (!textNode) {
85
+ return "";
86
+ }
87
+
88
+ return extractIndentation(textNode);
89
+ };
90
+
91
+ const extractIndentation = (textNode) => {
92
+ const text = textNode.value;
93
+ const lines = text.split(/\r?\n/);
94
+ const lastLine = lines[lines.length - 1];
95
+ if (lastLine.match(/^[\t ]+$/)) {
96
+ return lastLine;
97
+ }
98
+ return "";
99
+ };
100
+
101
+ const setIndentation = (htmlNodeText, indentation) => {
102
+ const contentIdentation = increaseIndentation(indentation, 2);
103
+ const lines = htmlNodeText.trimEnd().split(/\r?\n/);
104
+ let result = `\n`;
105
+ let i = 0;
106
+ while (i < lines.length) {
107
+ const line = lines[i];
108
+ i++;
109
+ result += `${contentIdentation}${line}\n`;
110
+ }
111
+ result += `${indentation}`;
112
+ return result;
113
+ };
114
+
115
+ export const increaseIndentation = (indentation, size) => {
116
+ const char = indentation[0];
117
+ return char ? `${indentation}${char.repeat(size)}` : " ".repeat(size);
118
+ };
@@ -1,90 +1,165 @@
1
- import { parse, serialize, parseFragment } from "parse5"
1
+ import { parse, serialize, parseFragment } from "parse5";
2
2
 
3
+ import { insertHtmlNodeAfter, insertHtmlNodeBefore } from "./html_node.js";
3
4
  import {
4
5
  getHtmlNodeAttribute,
5
6
  setHtmlNodeAttributes,
6
- } from "./html_node_attributes.js"
7
+ } from "./html_node_attributes.js";
7
8
  import {
8
9
  storeHtmlNodePosition,
9
10
  storeHtmlNodeAttributePosition,
10
- } from "./html_node_position.js"
11
- import { findHtmlChildNode, visitHtmlNodes } from "./html_search.js"
12
- import { getHtmlNodeText } from "./html_node_text.js"
11
+ } from "./html_node_position.js";
12
+ import { findHtmlChildNode, visitHtmlNodes } from "./html_search.js";
13
+ import { getHtmlNodeText } from "./html_node_text.js";
13
14
 
14
15
  export const parseHtmlString = (
15
16
  htmlString,
16
17
  { storeOriginalPositions = true } = {},
17
18
  ) => {
18
- const htmlAst = parse(htmlString, { sourceCodeLocationInfo: true })
19
+ const htmlAst = parse(htmlString, { sourceCodeLocationInfo: true });
19
20
  if (storeOriginalPositions) {
20
21
  const htmlNode = findHtmlChildNode(
21
22
  htmlAst,
22
23
  (node) => node.nodeName === "html",
23
- )
24
- const stored = getHtmlNodeAttribute(htmlNode, "original-position-stored")
24
+ );
25
+ const stored = getHtmlNodeAttribute(htmlNode, "original-position-stored");
25
26
  if (stored === undefined) {
26
27
  visitHtmlNodes(htmlAst, {
27
28
  "*": (node) => {
28
29
  if (node.nodeName === "script" || node.nodeName === "style") {
29
- const htmlNodeText = getHtmlNodeText(node)
30
+ const htmlNodeText = getHtmlNodeText(node);
30
31
  if (htmlNodeText !== undefined) {
31
- storeHtmlNodePosition(node)
32
+ storeHtmlNodePosition(node);
32
33
  }
33
34
  }
34
- storeHtmlNodeAttributePosition(node, "src")
35
- storeHtmlNodeAttributePosition(node, "href")
35
+ storeHtmlNodeAttributePosition(node, "src");
36
+ storeHtmlNodeAttributePosition(node, "href");
36
37
  },
37
- })
38
+ });
38
39
  setHtmlNodeAttributes(htmlNode, {
39
40
  "original-position-stored": "",
40
- })
41
+ });
41
42
  }
42
43
  }
43
- return htmlAst
44
- }
44
+ const htmlNode = htmlAst.childNodes.find((node) => node.nodeName === "html");
45
+ if (htmlNode) {
46
+ const bodyNode = htmlNode.childNodes.find(
47
+ (node) => node.nodeName === "body",
48
+ );
49
+ // for some reason "parse5" adds "\n\n" to the last text node of <body>
50
+ const lastBodyNode = bodyNode.childNodes[bodyNode.childNodes.length - 1];
51
+ if (
52
+ lastBodyNode &&
53
+ lastBodyNode.nodeName === "#text" &&
54
+ lastBodyNode.value.endsWith("\n\n")
55
+ ) {
56
+ lastBodyNode.value = lastBodyNode.value.slice(0, -2);
57
+ }
58
+ }
59
+ return htmlAst;
60
+ };
45
61
 
46
62
  export const stringifyHtmlAst = (
47
63
  htmlAst,
48
- { cleanupJsenvAttributes = false } = {},
64
+ { cleanupJsenvAttributes = false, cleanupPositionAttributes = false } = {},
49
65
  ) => {
50
- if (cleanupJsenvAttributes) {
66
+ if (cleanupJsenvAttributes || cleanupPositionAttributes) {
51
67
  const htmlNode = findHtmlChildNode(
52
68
  htmlAst,
53
69
  (node) => node.nodeName === "html",
54
- )
70
+ );
55
71
  const storedAttribute = getHtmlNodeAttribute(
56
72
  htmlNode,
57
73
  "original-position-stored",
58
- )
74
+ );
59
75
  if (storedAttribute !== undefined) {
60
76
  setHtmlNodeAttributes(htmlNode, {
61
77
  "original-position-stored": undefined,
62
- })
78
+ });
63
79
  visitHtmlNodes(htmlAst, {
64
80
  "*": (node) => {
65
81
  setHtmlNodeAttributes(node, {
66
82
  "original-position": undefined,
67
83
  "original-src-position": undefined,
68
84
  "original-href-position": undefined,
69
- "inlined-from-src": undefined,
70
- "inlined-from-href": undefined,
71
- "jsenv-cooked-by": undefined,
72
- "jsenv-inlined-by": undefined,
73
- "jsenv-injected-by": undefined,
74
- "jsenv-debug": undefined,
75
- })
85
+ ...(cleanupJsenvAttributes
86
+ ? {
87
+ "inlined-from-src": undefined,
88
+ "inlined-from-href": undefined,
89
+ "jsenv-cooked-by": undefined,
90
+ "jsenv-inlined-by": undefined,
91
+ "jsenv-injected-by": undefined,
92
+ "jsenv-debug": undefined,
93
+ "content-indented": undefined,
94
+ }
95
+ : {}),
96
+ });
76
97
  },
77
- })
98
+ });
99
+ }
100
+ }
101
+ // ensure body and html have \n
102
+ ensureLineBreaksBetweenHtmlNodes(htmlAst);
103
+ const htmlString = serialize(htmlAst);
104
+ return htmlString;
105
+ };
106
+
107
+ const ensureLineBreaksBetweenHtmlNodes = (rootNode) => {
108
+ const mutations = [];
109
+
110
+ const documentType = rootNode.childNodes[0];
111
+ if (documentType.nodeName === "#documentType") {
112
+ const html = rootNode.childNodes[1];
113
+ if (html.nodeName === "html") {
114
+ mutations.push(() => {
115
+ insertHtmlNodeAfter({ nodeName: "#text", value: "\n" }, documentType);
116
+ });
117
+
118
+ const head = html.childNodes[0];
119
+ if (head.nodeName === "head") {
120
+ mutations.push(() => {
121
+ insertHtmlNodeBefore({ nodeName: "#text", value: "\n " }, head);
122
+ });
123
+ }
124
+
125
+ const body = html.childNodes.find((node) => node.nodeName === "body");
126
+ if (body) {
127
+ const bodyLastChild = body.childNodes[body.childNodes.length - 1];
128
+ if (bodyLastChild.nodeName !== "#text") {
129
+ mutations.push(() => {
130
+ insertHtmlNodeAfter(
131
+ { nodeName: "#text", value: "\n " },
132
+ bodyLastChild,
133
+ );
134
+ });
135
+ }
136
+ if (
137
+ bodyLastChild.nodeName === "#text" &&
138
+ bodyLastChild.value[bodyLastChild.value.length - 1] === "\n"
139
+ ) {
140
+ bodyLastChild.value = bodyLastChild.value.slice(0, -1);
141
+ }
142
+ }
143
+
144
+ const htmlLastChild = html.childNodes[html.childNodes.length - 1];
145
+ if (htmlLastChild.nodeName !== "#text") {
146
+ mutations.push(() => {
147
+ insertHtmlNodeAfter(
148
+ { nodeName: "#text", value: "\n" },
149
+ htmlLastChild,
150
+ );
151
+ });
152
+ }
78
153
  }
79
154
  }
80
- const htmlString = serialize(htmlAst)
81
- return htmlString
82
- }
155
+
156
+ mutations.forEach((m) => m());
157
+ };
83
158
 
84
159
  export const parseSvgString = (svgString) => {
85
160
  const svgAst = parseFragment(svgString, {
86
161
  sourceCodeLocationInfo: true,
87
- })
88
- return svgAst
89
- }
90
- export const stringifySvgAst = stringifyHtmlAst
162
+ });
163
+ return svgAst;
164
+ };
165
+ export const stringifySvgAst = stringifyHtmlAst;
@@ -1,64 +1,64 @@
1
- import { getHtmlNodeAttribute } from "./html_node_attributes.js"
1
+ import { getHtmlNodeAttribute } from "./html_node_attributes.js";
2
2
 
3
3
  export const visitHtmlNodes = (htmlAst, visitors) => {
4
4
  const visitNode = (node) => {
5
- const visitor = visitors[node.nodeName] || visitors["*"]
5
+ const visitor = visitors[node.nodeName] || visitors["*"];
6
6
  if (visitor) {
7
- const callbackReturnValue = visitor(node)
7
+ const callbackReturnValue = visitor(node);
8
8
  if (callbackReturnValue === "stop") {
9
- return
9
+ return;
10
10
  }
11
11
  }
12
- const { childNodes } = node
12
+ const { childNodes } = node;
13
13
  if (childNodes) {
14
- let i = 0
14
+ let i = 0;
15
15
  while (i < childNodes.length) {
16
- visitNode(childNodes[i++])
16
+ visitNode(childNodes[i++]);
17
17
  }
18
18
  }
19
- }
20
- visitNode(htmlAst)
21
- }
19
+ };
20
+ visitNode(htmlAst);
21
+ };
22
22
 
23
23
  export const findHtmlNode = (htmlAst, predicate) => {
24
- let nodeMatching = null
24
+ let nodeMatching = null;
25
25
  visitHtmlNodes(htmlAst, {
26
26
  "*": (node) => {
27
27
  if (predicate(node)) {
28
- nodeMatching = node
29
- return "stop"
28
+ nodeMatching = node;
29
+ return "stop";
30
30
  }
31
- return null
31
+ return null;
32
32
  },
33
- })
34
- return nodeMatching
35
- }
33
+ });
34
+ return nodeMatching;
35
+ };
36
36
 
37
37
  export const findHtmlChildNode = (htmlNode, predicate) => {
38
- const { childNodes = [] } = htmlNode
39
- return childNodes.find(predicate)
40
- }
38
+ const { childNodes = [] } = htmlNode;
39
+ return childNodes.find(predicate);
40
+ };
41
41
 
42
42
  export const findHtmlNodes = (htmlAst, predicate) => {
43
- const nodes = []
43
+ const nodes = [];
44
44
  visitHtmlNodes(htmlAst, {
45
45
  "*": (node) => {
46
46
  if (predicate(node)) {
47
- nodes.push(node)
47
+ nodes.push(node);
48
48
  }
49
- return null
49
+ return null;
50
50
  },
51
- })
52
- return nodes
53
- }
51
+ });
52
+ return nodes;
53
+ };
54
54
 
55
55
  export const findHtmlNodeByTagName = (htmlAst, tagName) => {
56
- return findHtmlNode(htmlAst, (node) => node.nodeName === tagName)
57
- }
56
+ return findHtmlNode(htmlAst, (node) => node.nodeName === tagName);
57
+ };
58
58
 
59
59
  export const findHtmlNodeById = (htmlAst, id) => {
60
60
  return findHtmlNode(htmlAst, (node) => {
61
- const idCandidate = getHtmlNodeAttribute(node, "id")
62
- return idCandidate === id
63
- })
64
- }
61
+ const idCandidate = getHtmlNodeAttribute(node, "id");
62
+ return idCandidate === id;
63
+ });
64
+ };
@@ -1,18 +1,18 @@
1
1
  export const parseSrcSet = (srcset) => {
2
- const srcCandidates = []
2
+ const srcCandidates = [];
3
3
  srcset.split(",").forEach((set) => {
4
- const [specifier, descriptor] = set.trim().split(" ")
4
+ const [specifier, descriptor] = set.trim().split(" ");
5
5
  srcCandidates.push({
6
6
  specifier,
7
7
  descriptor,
8
- })
9
- })
10
- return srcCandidates
11
- }
8
+ });
9
+ });
10
+ return srcCandidates;
11
+ };
12
12
 
13
13
  export const stringifySrcSet = (srcCandidates) => {
14
14
  const srcset = srcCandidates
15
15
  .map(({ specifier, descriptor }) => `${specifier} ${descriptor}`)
16
- .join(", ")
17
- return srcset
18
- }
16
+ .join(", ");
17
+ return srcset;
18
+ };
@@ -3,9 +3,9 @@
3
3
  * - https://astexplorer.net/
4
4
  * - https://bvaughn.github.io/babel-repl
5
5
  */
6
- import { fileURLToPath } from "node:url"
6
+ import { fileURLToPath } from "node:url";
7
7
 
8
- import { createJsParseError } from "./js_parse_error.js"
8
+ import { createJsParseError } from "./js_parse_error.js";
9
9
 
10
10
  export const applyBabelPlugins = async ({
11
11
  babelPlugins,
@@ -17,18 +17,18 @@ export const applyBabelPlugins = async ({
17
17
  [urlInfo.type]: undefined,
18
18
  js_module: "module",
19
19
  js_classic: "classic",
20
- }[urlInfo.type]
21
- const url = urlInfo.originalUrl
22
- const generatedUrl = urlInfo.generatedUrl
23
- const content = urlInfo.content
20
+ }[urlInfo.type];
21
+ const url = urlInfo.originalUrl;
22
+ const generatedUrl = urlInfo.generatedUrl;
23
+ const content = urlInfo.content;
24
24
 
25
25
  if (babelPlugins.length === 0) {
26
- return { code: content }
26
+ return { code: content };
27
27
  }
28
- const { transformAsync, transformFromAstAsync } = await import("@babel/core")
28
+ const { transformAsync, transformFromAstAsync } = await import("@babel/core");
29
29
  const sourceFileName = url.startsWith("file:")
30
30
  ? fileURLToPath(url)
31
- : undefined
31
+ : undefined;
32
32
  options = {
33
33
  ast: false,
34
34
  // https://babeljs.io/docs/en/options#source-map-options
@@ -67,14 +67,14 @@ export const applyBabelPlugins = async ({
67
67
  },
68
68
  plugins: babelPlugins,
69
69
  ...options,
70
- }
70
+ };
71
71
  try {
72
72
  if (ast) {
73
- const result = await transformFromAstAsync(ast, content, options)
74
- return result
73
+ const result = await transformFromAstAsync(ast, content, options);
74
+ return result;
75
75
  }
76
- const result = await transformAsync(content, options)
77
- return result
76
+ const result = await transformAsync(content, options);
77
+ return result;
78
78
  } catch (error) {
79
79
  if (error && error.code === "BABEL_PARSE_ERROR") {
80
80
  throw createJsParseError({
@@ -84,16 +84,16 @@ export const applyBabelPlugins = async ({
84
84
  url,
85
85
  line: error.loc.line,
86
86
  column: error.loc.column,
87
- })
87
+ });
88
88
  }
89
- throw error
89
+ throw error;
90
90
  }
91
- }
91
+ };
92
92
 
93
93
  const useTypeScriptExtension = (url) => {
94
- const { pathname } = new URL(url)
95
- return pathname.endsWith(".ts") || pathname.endsWith(".tsx")
96
- }
94
+ const { pathname } = new URL(url);
95
+ return pathname.endsWith(".ts") || pathname.endsWith(".tsx");
96
+ };
97
97
 
98
98
  // const pattern = [
99
99
  // "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
@@ -1,4 +1,4 @@
1
- import { require } from "../require.js"
1
+ import { require } from "../require.js";
2
2
 
3
3
  // https://github.com/babel/babel/tree/master/packages/babel-helper-module-imports
4
4
  export const injectJsImport = ({
@@ -14,30 +14,30 @@ export const injectJsImport = ({
14
14
  addDefault,
15
15
  addNamed,
16
16
  addSideEffect,
17
- } = require("@babel/helper-module-imports")
17
+ } = require("@babel/helper-module-imports");
18
18
 
19
19
  if (namespace) {
20
20
  return addNamespace(programPath, from, {
21
21
  nameHint,
22
- })
22
+ });
23
23
  }
24
24
  if (name) {
25
- return addNamed(programPath, name, from)
25
+ return addNamed(programPath, name, from);
26
26
  }
27
27
  if (sideEffect) {
28
- return addSideEffect(programPath, from)
28
+ return addSideEffect(programPath, from);
29
29
  }
30
30
  return addDefault(programPath, from, {
31
31
  nameHint,
32
- })
33
- }
32
+ });
33
+ };
34
34
 
35
35
  const generateExpressionAst = (expression, options) => {
36
- const { parseExpression } = require("@babel/parser")
36
+ const { parseExpression } = require("@babel/parser");
37
37
 
38
- const ast = parseExpression(expression, options)
39
- return ast
40
- }
38
+ const ast = parseExpression(expression, options);
39
+ return ast;
40
+ };
41
41
 
42
42
  export const generateValueAst = (value) => {
43
43
  const valueAst = generateExpressionAst(
@@ -46,19 +46,19 @@ export const generateValueAst = (value) => {
46
46
  : value === null
47
47
  ? "null"
48
48
  : JSON.stringify(value),
49
- )
50
- return valueAst
51
- }
49
+ );
50
+ return valueAst;
51
+ };
52
52
 
53
53
  export const injectAstAfterImport = (programPath, ast) => {
54
- const bodyNodePaths = programPath.get("body")
54
+ const bodyNodePaths = programPath.get("body");
55
55
  const notAnImportIndex = bodyNodePaths.findIndex(
56
56
  (bodyNodePath) => bodyNodePath.node.type !== "ImportDeclaration",
57
- )
58
- const notAnImportNodePath = bodyNodePaths[notAnImportIndex]
57
+ );
58
+ const notAnImportNodePath = bodyNodePaths[notAnImportIndex];
59
59
  if (notAnImportNodePath) {
60
- notAnImportNodePath.insertBefore(ast)
60
+ notAnImportNodePath.insertBefore(ast);
61
61
  } else {
62
- bodyNodePaths[0].insertBefore(ast)
62
+ bodyNodePaths[0].insertBefore(ast);
63
63
  }
64
- }
64
+ };
@@ -5,11 +5,11 @@ export const createJsParseError = ({
5
5
  line,
6
6
  column,
7
7
  }) => {
8
- const parseError = new Error(message)
9
- parseError.reasonCode = reasonCode
10
- parseError.code = "PARSE_ERROR"
11
- parseError.url = url
12
- parseError.line = line
13
- parseError.column = column
14
- return parseError
15
- }
8
+ const parseError = new Error(message);
9
+ parseError.reasonCode = reasonCode;
10
+ parseError.code = "PARSE_ERROR";
11
+ parseError.url = url;
12
+ parseError.line = line;
13
+ parseError.column = column;
14
+ return parseError;
15
+ };
@@ -1,17 +1,17 @@
1
1
  export const getTypePropertyNode = (node) => {
2
2
  if (node.type !== "ObjectExpression") {
3
- return null
3
+ return null;
4
4
  }
5
- const { properties } = node
5
+ const { properties } = node;
6
6
  return properties.find((property) => {
7
7
  return (
8
8
  property.type === "Property" &&
9
9
  property.key.type === "Identifier" &&
10
10
  property.key.name === "type"
11
- )
12
- })
13
- }
11
+ );
12
+ });
13
+ };
14
14
 
15
15
  export const isStringLiteralNode = (node) => {
16
- return node.type === "Literal" && typeof node.value === "string"
17
- }
16
+ return node.type === "Literal" && typeof node.value === "string";
17
+ };