@doc-kit/core 0.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +36 -0
  4. package/package.json +88 -0
  5. package/shiki.config.mjs +50 -0
  6. package/src/caching.mjs +183 -0
  7. package/src/generators/ast/README.md +8 -0
  8. package/src/generators/ast/constants.mjs +1 -0
  9. package/src/generators/ast/generate.mjs +125 -0
  10. package/src/generators/ast/index.mjs +20 -0
  11. package/src/generators/ast/types.d.ts +7 -0
  12. package/src/generators/ast-js/README.md +8 -0
  13. package/src/generators/ast-js/generate.mjs +56 -0
  14. package/src/generators/ast-js/index.mjs +24 -0
  15. package/src/generators/ast-js/types.d.ts +5 -0
  16. package/src/generators/index.mjs +48 -0
  17. package/src/generators/json-simple/README.md +9 -0
  18. package/src/generators/json-simple/generate.mjs +33 -0
  19. package/src/generators/json-simple/index.mjs +23 -0
  20. package/src/generators/json-simple/types.d.ts +6 -0
  21. package/src/generators/loader.mjs +96 -0
  22. package/src/generators/metadata/README.md +8 -0
  23. package/src/generators/metadata/constants.mjs +68 -0
  24. package/src/generators/metadata/generate.mjs +40 -0
  25. package/src/generators/metadata/index.mjs +21 -0
  26. package/src/generators/metadata/maps/builtin.json +16 -0
  27. package/src/generators/metadata/maps/mdn.json +1114 -0
  28. package/src/generators/metadata/types.d.ts +145 -0
  29. package/src/generators/metadata/utils/parse.mjs +160 -0
  30. package/src/generators/metadata/utils/resolveTypes.mjs +204 -0
  31. package/src/generators/metadata/utils/slugger.mjs +39 -0
  32. package/src/generators/metadata/utils/transformers.mjs +109 -0
  33. package/src/generators/metadata/utils/visitors.mjs +134 -0
  34. package/src/generators/metadata/utils/yaml.mjs +58 -0
  35. package/src/generators/types.d.ts +129 -0
  36. package/src/generators.mjs +145 -0
  37. package/src/logger/constants.mjs +34 -0
  38. package/src/logger/index.mjs +27 -0
  39. package/src/logger/logger.mjs +177 -0
  40. package/src/logger/transports/console.mjs +54 -0
  41. package/src/logger/transports/github.mjs +42 -0
  42. package/src/logger/transports/index.mjs +11 -0
  43. package/src/logger/types.d.ts +29 -0
  44. package/src/logger/utils/colors.mjs +18 -0
  45. package/src/logger/utils/time.mjs +21 -0
  46. package/src/parsers/markdown.mjs +49 -0
  47. package/src/parsers/types.d.ts +7 -0
  48. package/src/threading/chunk-worker.mjs +23 -0
  49. package/src/threading/index.mjs +27 -0
  50. package/src/threading/parallel.mjs +140 -0
  51. package/src/utils/array.mjs +7 -0
  52. package/src/utils/configuration/index.mjs +263 -0
  53. package/src/utils/configuration/templates.mjs +21 -0
  54. package/src/utils/configuration/types.d.ts +64 -0
  55. package/src/utils/file.mjs +20 -0
  56. package/src/utils/generators.mjs +71 -0
  57. package/src/utils/highlighter.mjs +198 -0
  58. package/src/utils/html-minifier.mjs +12 -0
  59. package/src/utils/loaders.mjs +48 -0
  60. package/src/utils/misc.mjs +72 -0
  61. package/src/utils/queries/constants.mjs +3 -0
  62. package/src/utils/queries/index.mjs +93 -0
  63. package/src/utils/queries/utils.mjs +45 -0
  64. package/src/utils/remark.mjs +97 -0
  65. package/src/utils/signature/constants.mjs +12 -0
  66. package/src/utils/signature/parseList.mjs +133 -0
  67. package/src/utils/signature/parseSignature.mjs +208 -0
  68. package/src/utils/signature/types.d.ts +66 -0
  69. package/src/utils/swc.mjs +34 -0
  70. package/src/utils/type-annotations/hast.mjs +115 -0
  71. package/src/utils/type-annotations/mdast.mjs +75 -0
  72. package/src/utils/type-annotations/remark.mjs +24 -0
  73. package/src/utils/type-annotations/syntax.mjs +188 -0
  74. package/src/utils/unist.mjs +75 -0
  75. package/src/utils/url.mjs +27 -0
@@ -0,0 +1,48 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { extname, isAbsolute, join } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ /**
6
+ * Converts a value to a parsed URL object.
7
+ *
8
+ * @param {string|URL} url - The URL string or URL object to parse
9
+ * @returns {URL|null} The parsed URL object, or null if parsing fails
10
+ */
11
+ export const toParsedURL = url => (url instanceof URL ? url : URL.parse(url));
12
+
13
+ /**
14
+ * Loads content from a URL or file path
15
+ * @param {string|URL} url The URL or file path to load
16
+ * @returns {Promise<string>} The content as a string
17
+ */
18
+ export const loadFromURL = async url => {
19
+ const parsedUrl = toParsedURL(url);
20
+
21
+ if (!parsedUrl || parsedUrl.protocol === 'file:') {
22
+ // Load from file system
23
+ return readFile(parsedUrl ?? url, 'utf-8');
24
+ } else {
25
+ // Load from network
26
+ const response = await fetch(parsedUrl);
27
+ return response.text();
28
+ }
29
+ };
30
+
31
+ /**
32
+ * Dynamically imports a module from a URL, using JSON import assertion if applicable.
33
+ *
34
+ * @param {string|URL} url - The URL of the module to import
35
+ * @returns {Promise<any>} The imported module
36
+ */
37
+ export const importFromURL = async url => {
38
+ const useJSONAssertion = extname(String(url)) === '.json';
39
+
40
+ const parsed = toParsedURL(url);
41
+
42
+ const imported = await import(
43
+ parsed ?? pathToFileURL(isAbsolute(url) ? url : join(process.cwd(), url)),
44
+ useJSONAssertion ? { with: { type: 'json' } } : {}
45
+ );
46
+
47
+ return imported.default ?? imported;
48
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Creates a lazy-initialized function that caches the result of the first invocation.
3
+ * @template {(...args: any[]) => any} T
4
+ * @param {T} fn - The function to be lazily executed
5
+ * @returns {T} A wrapper function that lazily executes fn and caches its result
6
+ */
7
+ export const lazy = fn =>
8
+ (
9
+ c =>
10
+ (...args) =>
11
+ (c ??= fn(...args))
12
+ )();
13
+
14
+ /**
15
+ * Checks if a value is a plain JavaScript object.
16
+ * @param {*} value - The value to check
17
+ * @returns {boolean} True if the value is a plain object, false otherwise
18
+ */
19
+ export const isPlainObject = value => {
20
+ if (value === null || typeof value !== 'object') {
21
+ return false;
22
+ }
23
+
24
+ const prototype = Object.getPrototypeOf(value);
25
+ return prototype === Object.prototype || prototype === null;
26
+ };
27
+
28
+ /**
29
+ * Checks if a value is an async generator/iterable.
30
+ * @param {unknown} obj - Value to check
31
+ * @returns {obj is AsyncGenerator} True if the value is an async iterable
32
+ */
33
+ export const isAsyncIterable = obj =>
34
+ obj !== null &&
35
+ typeof obj === 'object' &&
36
+ typeof obj[Symbol.asyncIterator] === 'function';
37
+
38
+ /**
39
+ * Returns a shallow copy of `obj` without the specified keys.
40
+ * @param {Record<string, any>} obj
41
+ * @param {string[]} keys - Keys to exclude
42
+ * @returns {Record<string, any>}
43
+ */
44
+ export const omitKeys = (obj, keys = []) =>
45
+ Object.fromEntries(
46
+ Object.entries(obj).filter(([key]) => !keys.includes(key))
47
+ );
48
+
49
+ /**
50
+ * Recursively merges plain objects from left to right.
51
+ * @template T
52
+ * @param {...T} objects - Any number of objects to merge
53
+ * @returns {T} A new object containing the deep merge of all provided objects
54
+ */
55
+ export const deepMerge = (...objects) => {
56
+ return objects.reduce((result, source) => {
57
+ for (const [key, sourceValue] of Object.entries(source)) {
58
+ if (sourceValue === undefined) {
59
+ continue;
60
+ }
61
+
62
+ const targetValue = result[key];
63
+ result[key] = isPlainObject(sourceValue)
64
+ ? deepMerge(isPlainObject(targetValue) ? targetValue : {}, sourceValue)
65
+ : Array.isArray(sourceValue)
66
+ ? sourceValue.slice()
67
+ : sourceValue;
68
+ }
69
+
70
+ return result;
71
+ }, {});
72
+ };
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ export const VALID_JAVASCRIPT_PROPERTY = /^[.a-z0-9$_'-]+$/i;
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ import { transformNodesToString } from '../unist.mjs';
4
+ import { isTypedList } from './utils.mjs';
5
+
6
+ // This defines the actual REGEX Queries
7
+ export const QUERIES = {
8
+ // Fixes the references to Markdown pages into the API documentation
9
+ markdownUrl: /^(?![+a-zA-Z]+:)([^#?]+)\.md(#.+)?$/,
10
+ // ReGeX for handling Stability Indexes Metadata
11
+ stabilityIndex: /^Stability: ([0-5](?:\.[0-3])?)(?:\s*-\s*)?(.*)$/s,
12
+ // ReGeX for handling the Stability Index Prefix
13
+ stabilityIndexPrefix: /Stability: ([0-5](?:\.[0-3])?)/,
14
+ // ReGeX for retrieving the inner content from a YAML block
15
+ yamlInnerContent: /^<!--[ ]?(?:YAML([\s\S]*?)|([ \S]*?))?[ ]?-->/,
16
+ // ReGeX for standard Markdown YAML frontmatter
17
+ standardYamlFrontmatter: /^---\r?\n([\s\S]*?)\r?\n---/,
18
+ // ReGeX for finding references to Unix manuals
19
+ unixManualPage: /\b([a-z.]+)\((\d)([a-z]?)\)/g,
20
+ // ReGeX for determing a typed list's non-property names
21
+ typedListStarters: /^(Returns|Extends|Type):?\s*/,
22
+ };
23
+
24
+ export const UNIST = {
25
+ /**
26
+ * @param {import('@types/mdast').Blockquote} blockquote
27
+ * @returns {boolean}
28
+ */
29
+ isStabilityNode: ({ type, children }) =>
30
+ type === 'blockquote' &&
31
+ QUERIES.stabilityIndex.test(transformNodesToString(children)),
32
+
33
+ /**
34
+ * @param {import('@types/mdast').Html} html
35
+ * @returns {boolean}
36
+ */
37
+ isYamlNode: ({ type, value }) =>
38
+ type === 'html' && QUERIES.yamlInnerContent.test(value),
39
+
40
+ /**
41
+ * @param {import('@types/mdast').Text} text
42
+ * @returns {boolean}
43
+ */
44
+ isTextWithUnixManual: ({ type, value }) =>
45
+ type === 'text' && QUERIES.unixManualPage.test(value),
46
+
47
+ /**
48
+ * @param {import('@types/mdast').Link} link
49
+ * @returns {boolean}
50
+ */
51
+ isMarkdownUrl: ({ type, url }) =>
52
+ type === 'link' && QUERIES.markdownUrl.test(url),
53
+
54
+ /**
55
+ * @param {import('@types/mdast').Heading} heading
56
+ * @returns {boolean}
57
+ */
58
+ isHeading: ({ type, depth }) =>
59
+ type === 'heading' && depth >= 1 && depth <= 5,
60
+
61
+ /**
62
+ * @param {import('@types/mdast').LinkReference} linkReference
63
+ * @returns {boolean}
64
+ */
65
+ isLinkReference: ({ type, identifier }) =>
66
+ type === 'linkReference' && !!identifier,
67
+
68
+ /**
69
+ * @param {import('@types/mdast').List} list
70
+ * @returns {boolean}
71
+ */
72
+ isLooselyTypedList: list => Boolean(isTypedList(list)),
73
+
74
+ /**
75
+ * @param {import('@types/mdast').List} list
76
+ * @returns {boolean}
77
+ */
78
+ isStronglyTypedList: list => {
79
+ const confidence = isTypedList(list);
80
+
81
+ if (confidence === 1) {
82
+ // This is a loosely typed list, but we can still check if it is strongly typed.
83
+ const [, secondNode, thirdNode] =
84
+ list.children?.[0]?.children?.[0]?.children ?? [];
85
+
86
+ return (
87
+ secondNode?.value?.trim() === '' && thirdNode?.type === 'typeAnnotation'
88
+ );
89
+ }
90
+
91
+ return Boolean(confidence);
92
+ },
93
+ };
@@ -0,0 +1,45 @@
1
+ import { VALID_JAVASCRIPT_PROPERTY } from './constants.mjs';
2
+ import { QUERIES } from './index.mjs';
3
+
4
+ /**
5
+ * @param {import('@types/mdast').List} list
6
+ * @returns {0 | 1 | 2} confidence
7
+ *
8
+ * 0: This is not a typed list
9
+ * 1: This is a loosely typed list
10
+ * 2: This is a strongly typed list
11
+ */
12
+ export const isTypedList = list => {
13
+ if (!list || list.type !== 'list') {
14
+ return 0;
15
+ }
16
+
17
+ const firstNode = list.children?.[0]?.children?.[0]?.children[0];
18
+
19
+ if (!firstNode) {
20
+ return 0;
21
+ }
22
+
23
+ const value = firstNode?.value?.trimStart();
24
+
25
+ // Typed list starters (strong signal)
26
+ if (value && QUERIES.typedListStarters.test(value)) {
27
+ return 2;
28
+ }
29
+
30
+ // Direct type annotation: {Type}
31
+ if (firstNode.type === 'typeAnnotation') {
32
+ return 2;
33
+ }
34
+
35
+ // inlineCode + space (weaker signal)
36
+ if (
37
+ firstNode.type === 'inlineCode' &&
38
+ value &&
39
+ VALID_JAVASCRIPT_PROPERTY.test(value)
40
+ ) {
41
+ return 1;
42
+ }
43
+
44
+ return 0;
45
+ };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ import rehypeStringify from 'rehype-stringify';
4
+ import remarkGfm from 'remark-gfm';
5
+ import remarkMdx from 'remark-mdx';
6
+ import remarkParse from 'remark-parse';
7
+ import remarkRehype from 'remark-rehype';
8
+ import remarkStringify from 'remark-stringify';
9
+ import { unified } from 'unified';
10
+
11
+ import syntaxHighlighter from './highlighter.mjs';
12
+ import { lazy } from './misc.mjs';
13
+ import { typeAnnotationToHast } from './type-annotations/hast.mjs';
14
+ import remarkTypeAnnotations from './type-annotations/remark.mjs';
15
+
16
+ // MDX node types that may appear in trees parsed by `getRemarkMdx`; the
17
+ // rehype pipelines pass them through untouched.
18
+ const passThrough = [
19
+ 'element',
20
+ 'mdxJsxTextElement',
21
+ 'mdxJsxFlowElement',
22
+ 'mdxJsxAttribute',
23
+ 'mdxJsxAttributeValueExpression',
24
+ 'mdxFlowExpression',
25
+ 'mdxTextExpression',
26
+ 'mdxjsEsm',
27
+ ];
28
+
29
+ /**
30
+ * Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown)
31
+ * plus `{...}` type annotations (see `./type-annotations`), which only exist
32
+ * in non-MDX files — the MDX pipeline below never registers them.
33
+ */
34
+ export const getRemark = lazy(() =>
35
+ unified()
36
+ .use(remarkParse)
37
+ .use(remarkTypeAnnotations)
38
+ .use(remarkGfm)
39
+ .use(remarkStringify)
40
+ );
41
+
42
+ /**
43
+ * Retrieves an instance of Remark configured to parse MDX (JSX-in-Markdown).
44
+ *
45
+ * Unlike {@link getRemark}, this understands `<Component />` and `{expression}`
46
+ * syntax as real JSX/expression nodes. It is only used for `.mdx` (or
47
+ * explicitly opted-in) files, since Node.js core `.md` files use bare `<` and
48
+ * `{` for type annotations that MDX would otherwise try to parse.
49
+ */
50
+ export const getRemarkMdx = lazy(() =>
51
+ unified().use(remarkParse).use(remarkMdx).use(remarkGfm)
52
+ );
53
+
54
+ /**
55
+ * Retrieves an instance of Remark configured to output stringified HTML code
56
+ */
57
+ export const getRemarkRehype = lazy(() =>
58
+ unified()
59
+ .use(remarkParse)
60
+ // We make Rehype ignore existing HTML nodes (just the node itself, not its children)
61
+ // as these are nodes we manually created during the rehype process
62
+ // We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
63
+ // and we trust the sources of the Markdown files
64
+ .use(remarkRehype, {
65
+ allowDangerousHtml: true,
66
+ passThrough,
67
+ handlers: { typeAnnotation: typeAnnotationToHast },
68
+ })
69
+ // We allow dangerous HTML to be passed through, since we have HTML within our Markdown
70
+ // and we trust the sources of the Markdown files
71
+ .use(rehypeStringify, { allowDangerousHtml: true })
72
+ );
73
+
74
+ /**
75
+ * Retrieves an instance of Remark configured to output stringified HTML code
76
+ * including parsing Code Boxes with syntax highlighting
77
+ */
78
+ export const getRemarkRehypeWithShiki = lazy(() =>
79
+ unified()
80
+ .use(remarkParse)
81
+ // We make Rehype ignore existing HTML nodes (just the node itself, not its children)
82
+ // as these are nodes we manually created during the rehype process
83
+ // We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
84
+ // and we trust the sources of the Markdown files
85
+ .use(remarkRehype, {
86
+ allowDangerousHtml: true,
87
+ passThrough,
88
+ // legacy-html gets the minimal (unhighlighted) type rendering
89
+ handlers: { typeAnnotation: typeAnnotationToHast },
90
+ })
91
+ // This is a custom ad-hoc within the Shiki Rehype plugin, used to highlight code
92
+ // and transform them into HAST nodes
93
+ .use(syntaxHighlighter)
94
+ // We allow dangerous HTML to be passed through, since we have HTML within our Markdown
95
+ // and we trust the sources of the Markdown files
96
+ .use(rehypeStringify, { allowDangerousHtml: true })
97
+ );
@@ -0,0 +1,12 @@
1
+ // Grabs a method's name
2
+ export const NAME_EXPRESSION = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
3
+
4
+ // Checks if there's a leading hyphen
5
+ export const LEADING_HYPHEN = /^-\s*/;
6
+
7
+ // Grabs the default value if present
8
+ export const DEFAULT_EXPRESSION = /\s*\*\*Default:\*\*\s*([^]+)$/i;
9
+
10
+ // Grabs the parameters from a method's signature
11
+ // ex/ 'new buffer.Blob([sources[, options]])'.match(PARAM_EXPRESSION) === ['([sources[, options]])', '[sources[, options]]']
12
+ export const PARAM_EXPRESSION = /\(([^)]+)\);?$/;
@@ -0,0 +1,133 @@
1
+ import {
2
+ DEFAULT_EXPRESSION,
3
+ LEADING_HYPHEN,
4
+ NAME_EXPRESSION,
5
+ } from './constants.mjs';
6
+ import parseSignature from './parseSignature.mjs';
7
+ import { leftHandAssign } from '../generators.mjs';
8
+ import { QUERIES, UNIST } from '../queries/index.mjs';
9
+ import { transformNodesToString } from '../unist.mjs';
10
+
11
+ /**
12
+ * Extracts and removes a specific pattern from a text string while storing the result in a key of the `current` object.
13
+ * @param {string} text
14
+ * @param {RegExp} pattern
15
+ * @param {string} key
16
+ * @param {Object} current
17
+ * @returns {string}
18
+ */
19
+ export const extractPattern = (text, pattern, key, current) => {
20
+ const match = text.match(pattern)?.[1]?.trim().replace(/\.$/, '');
21
+
22
+ if (!match) {
23
+ return text;
24
+ }
25
+
26
+ current[key] = match;
27
+ return text.replace(pattern, '');
28
+ };
29
+
30
+ /**
31
+ * Parses an individual list item node to extract its properties
32
+ *
33
+ * @param {import('@types/mdast').ListItem} child
34
+ * @returns {import('./types').ParameterList}
35
+ */
36
+ export function parseListItem(child) {
37
+ const current = {};
38
+
39
+ const subList = child.children.find(UNIST.isLooselyTypedList);
40
+
41
+ // Extract and clean raw text from the node, excluding nested lists.
42
+ // Type annotations serialize back to their `{Type}` source form.
43
+ current.textRaw = transformNodesToString(
44
+ child.children.filter(node => node !== subList)
45
+ )
46
+ .replace(/\s+/g, ' ')
47
+ .replace(/<!--.*?-->/gs, '');
48
+
49
+ let text = current.textRaw;
50
+
51
+ // Identify return items or extract key properties (name, type, default) from the text
52
+ const starter = text.match(QUERIES.typedListStarters);
53
+ if (starter) {
54
+ current.name =
55
+ starter[1] === 'Returns' ? 'return' : starter[1].toLowerCase();
56
+ text = text.slice(starter[0].length);
57
+ } else {
58
+ text = extractPattern(text, NAME_EXPRESSION, 'name', current);
59
+ }
60
+
61
+ // The type is the item's typeAnnotation node; it's only a type (and not
62
+ // prose) when its serialized form sits at the head of the remaining text
63
+ const annotation = child.children
64
+ .find(node => node.type === 'paragraph')
65
+ ?.children.find(node => node.type === 'typeAnnotation');
66
+
67
+ if (annotation && text.startsWith(`{${annotation.value}}`)) {
68
+ current.type = annotation.value;
69
+ text = text.slice(annotation.value.length + 2).replace(/^\s+/, '');
70
+ }
71
+
72
+ text = extractPattern(text, DEFAULT_EXPRESSION, 'default', current);
73
+
74
+ // Set the remaining text as the description, removing any leading hyphen
75
+ current.desc = text.replace(LEADING_HYPHEN, '').trim() || undefined;
76
+
77
+ // Parse nested lists (options) recursively if present
78
+ if (subList) {
79
+ current.options = subList.children.map(parseListItem);
80
+ }
81
+
82
+ return current;
83
+ }
84
+
85
+ /**
86
+ * Parses a list of nodes and updates the corresponding section object with the extracted information.
87
+ * Handles different section types such as methods, properties, and events differently.
88
+ * @param {{ [key: string]: unknown }} section - The section object to populate with the parsed values
89
+ * @param {import('@types/mdast').RootContent[]} nodes
90
+ */
91
+ export function parseList(section, nodes) {
92
+ const listIdx = nodes.findIndex(UNIST.isStronglyTypedList);
93
+ const list = nodes[listIdx];
94
+
95
+ const values = list ? list.children.map(parseListItem) : [];
96
+
97
+ let removeList = true;
98
+
99
+ // Update the section based on its type and parsed values
100
+ switch (section.type) {
101
+ case 'ctor':
102
+ // Constructors are their own signatures
103
+ leftHandAssign(section, parseSignature(section.textRaw, values));
104
+ break;
105
+
106
+ case 'classMethod':
107
+ case 'method':
108
+ // For methods and constructors, parse and attach signatures
109
+ section.signatures = [parseSignature(section.textRaw, values)];
110
+ break;
111
+
112
+ case 'property':
113
+ // For properties, update type and other details if values exist
114
+ if (values.length) {
115
+ delete values[0].name;
116
+
117
+ Object.assign(section, values[0]);
118
+ }
119
+ break;
120
+
121
+ case 'event':
122
+ // For events, assign parsed values as parameters
123
+ section.params = values;
124
+ break;
125
+
126
+ default:
127
+ removeList = false;
128
+ }
129
+
130
+ if (removeList && list) {
131
+ nodes.splice(listIdx, 1);
132
+ }
133
+ }