@markuplint/parser-utils 4.6.8 → 4.7.1

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/CHANGELOG.md CHANGED
@@ -3,13 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [4.6.8](https://github.com/markuplint/markuplint/compare/@markuplint/parser-utils@4.6.7...@markuplint/parser-utils@4.6.8) (2024-10-14)
6
+ ## [4.7.1](https://github.com/markuplint/markuplint/compare/@markuplint/parser-utils@4.7.0...@markuplint/parser-utils@4.7.1) (2024-10-27)
7
+
8
+
9
+ ### Performance Improvements
10
+
11
+ * **parser-utils:** adjusted siblings correction timing to reduce exponential complexity ([676357c](https://github.com/markuplint/markuplint/commit/676357c438df7545f472787c9032463f9fdba515))
12
+
7
13
 
8
- **Note:** Version bump only for package @markuplint/parser-utils
9
14
 
10
15
 
11
16
 
17
+ # [4.7.0](https://github.com/markuplint/markuplint/compare/@markuplint/parser-utils@4.6.8...@markuplint/parser-utils@4.7.0) (2024-10-15)
12
18
 
19
+ ### Features
20
+
21
+ - **parser-utils:** expose `getOffsetsFromCode` function ([8ef7aec](https://github.com/markuplint/markuplint/commit/8ef7aec26d3198328c86ebeffaa0bd9c879a1f0e))
22
+
23
+ ## [4.6.8](https://github.com/markuplint/markuplint/compare/@markuplint/parser-utils@4.6.7...@markuplint/parser-utils@4.6.8) (2024-10-14)
24
+
25
+ **Note:** Version bump only for package @markuplint/parser-utils
13
26
 
14
27
  ## [4.6.7](https://github.com/markuplint/markuplint/compare/@markuplint/parser-utils@4.6.6...@markuplint/parser-utils@4.6.7) (2024-09-23)
15
28
 
package/lib/debug.d.ts CHANGED
@@ -2,3 +2,8 @@ import type { MLASTNode } from '@markuplint/ml-ast';
2
2
  import debug from 'debug';
3
3
  export declare const log: debug.Debugger;
4
4
  export declare function domLog(nodeList: readonly (MLASTNode | null)[]): void;
5
+ export declare class PerformanceTimer {
6
+ #private;
7
+ push(name?: string): "" | undefined;
8
+ log(): void;
9
+ }
package/lib/debug.js CHANGED
@@ -1,6 +1,62 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _PerformanceTimer_logs, _PerformanceTimer_counter;
1
13
  import debug from 'debug';
2
14
  import { nodeListToDebugMaps } from './debugger.js';
3
15
  export const log = debug('ml-parser');
4
16
  export function domLog(nodeList) {
5
17
  log('Parse result: %O', nodeListToDebugMaps(nodeList, true));
6
18
  }
19
+ export class PerformanceTimer {
20
+ constructor() {
21
+ _PerformanceTimer_logs.set(this, []);
22
+ _PerformanceTimer_counter.set(this, -1);
23
+ }
24
+ push(name) {
25
+ var _a;
26
+ if (!log.enabled) {
27
+ return '';
28
+ }
29
+ __classPrivateFieldSet(this, _PerformanceTimer_counter, (_a = __classPrivateFieldGet(this, _PerformanceTimer_counter, "f"), _a++, _a), "f");
30
+ const now = performance.now();
31
+ const last = __classPrivateFieldGet(this, _PerformanceTimer_logs, "f").at(-1);
32
+ if (last && Number.isNaN(last[2])) {
33
+ last[2] = now;
34
+ }
35
+ name = name || `#${__classPrivateFieldGet(this, _PerformanceTimer_counter, "f")}`;
36
+ __classPrivateFieldGet(this, _PerformanceTimer_logs, "f").push([name, now, Number.NaN]);
37
+ }
38
+ log() {
39
+ if (!log.enabled) {
40
+ return;
41
+ }
42
+ this.push('end');
43
+ __classPrivateFieldGet(this, _PerformanceTimer_logs, "f").pop();
44
+ const map = new Map();
45
+ for (const content of __classPrivateFieldGet(this, _PerformanceTimer_logs, "f")) {
46
+ const diff = content[2] - content[1];
47
+ const name = content[0];
48
+ if (map.has(name)) {
49
+ const [total, count] = map.get(name);
50
+ map.set(name, [total + diff, count + 1]);
51
+ }
52
+ else {
53
+ map.set(name, [diff, 1]);
54
+ }
55
+ }
56
+ for (const [name, [total, count]] of map) {
57
+ const avg = total / count;
58
+ log.extend(name)('%dms (avg: %dms, count: %d)', total.toExponential(3), avg.toExponential(3), count);
59
+ }
60
+ }
61
+ }
62
+ _PerformanceTimer_logs = new WeakMap(), _PerformanceTimer_counter = new WeakMap();
@@ -12,3 +12,7 @@ export declare function getPosition(rawCodeFragment: string, startOffset: number
12
12
  };
13
13
  export declare function getEndLine(rawCodeFragment: string, startLine: number): number;
14
14
  export declare function getEndCol(rawCodeFragment: string, startCol: number): number;
15
+ export declare function getOffsetsFromCode(rawCode: string, startLine: number, startCol: number, endLine: number, endCol: number): {
16
+ offset: number;
17
+ endOffset: number;
18
+ };
@@ -27,3 +27,25 @@ export function getEndCol(rawCodeFragment, startCol) {
27
27
  const lastLine = lines.pop();
28
28
  return lineCount > 1 ? lastLine.length + 1 : startCol + rawCodeFragment.length;
29
29
  }
30
+ export function getOffsetsFromCode(rawCode, startLine, startCol, endLine, endCol) {
31
+ const lines = rawCode.split('\n');
32
+ let offset = 0;
33
+ let endOffset = 0;
34
+ for (let i = 0; i < startLine - 1; i++) {
35
+ const line = lines[i];
36
+ if (line == null) {
37
+ continue;
38
+ }
39
+ offset += line.length + 1;
40
+ }
41
+ offset += startCol - 1;
42
+ for (let i = 0; i < endLine - 1; i++) {
43
+ const line = lines[i];
44
+ if (line == null) {
45
+ continue;
46
+ }
47
+ endOffset += line.length + 1;
48
+ }
49
+ endOffset += endCol - 1;
50
+ return { offset, endOffset };
51
+ }
package/lib/parser.js CHANGED
@@ -9,19 +9,20 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _Parser_instances, _Parser_booleanish, _Parser_defaultState, _Parser_endTagType, _Parser_ignoreTags, _Parser_maskChar, _Parser_tagNameCaseSensitive, _Parser_selfCloseType, _Parser_spaceChars, _Parser_rawTextElements, _Parser_authoredElementName, _Parser_originalRawCode, _Parser_rawCode, _Parser_defaultDepth, _Parser_walkMethodSequentailPrevNode, _Parser_arrayize, _Parser_concatText, _Parser_concatTextNodes, _Parser_convertIntoInvalidNode, _Parser_createOffsetSpaces, _Parser_createRemnantNode, _Parser_exposeRemnantNodes, _Parser_getEndLocation, _Parser_orphanEndTagToBogusMark, _Parser_pairing, _Parser_parseEndTag, _Parser_parseStartTag, _Parser_parseTag, _Parser_removeChild, _Parser_removeDeprecatedNode, _Parser_removeOffsetSpaces, _Parser_reset, _Parser_setRawCode, _Parser_siblingsCorrection, _Parser_trimText;
12
+ var _Parser_instances, _Parser_booleanish, _Parser_defaultState, _Parser_endTagType, _Parser_ignoreTags, _Parser_maskChar, _Parser_tagNameCaseSensitive, _Parser_selfCloseType, _Parser_spaceChars, _Parser_rawTextElements, _Parser_authoredElementName, _Parser_originalRawCode, _Parser_rawCode, _Parser_defaultDepth, _Parser_walkMethodSequentailPrevNode, _Parser_arrayize, _Parser_concatText, _Parser_concatTextNodes, _Parser_convertIntoInvalidNode, _Parser_createOffsetSpaces, _Parser_createRemnantNode, _Parser_exposeRemnantNodes, _Parser_getEndLocation, _Parser_orphanEndTagToBogusMark, _Parser_pairing, _Parser_parseEndTag, _Parser_parseStartTag, _Parser_parseTag, _Parser_removeChild, _Parser_removeDeprecatedNode, _Parser_removeOffsetSpaces, _Parser_reset, _Parser_setRawCode, _Parser_trimText;
13
13
  import { isVoidElement as detectVoidElement } from '@markuplint/ml-spec';
14
14
  import { v4 as uuid } from 'uuid';
15
15
  import { attrTokenizer } from './attr-tokenizer.js';
16
16
  import { defaultSpaces } from './const.js';
17
- import { domLog } from './debug.js';
17
+ import { domLog, PerformanceTimer } from './debug.js';
18
18
  import { detectElementType } from './detect-element-type.js';
19
19
  import { AttrState, TagState } from './enums.js';
20
- import { getEndCol, getEndLine, getPosition } from './get-location.js';
20
+ import { getEndCol, getEndLine, getOffsetsFromCode, getPosition } from './get-location.js';
21
21
  import { ignoreBlock, restoreNode } from './ignore-block.js';
22
22
  import { ignoreFrontMatter } from './ignore-front-matter.js';
23
23
  import { ParserError } from './parser-error.js';
24
24
  import { sortNodes } from './sort-nodes.js';
25
+ const timer = new PerformanceTimer();
25
26
  export class Parser {
26
27
  constructor(options, defaultState) {
27
28
  _Parser_instances.add(this);
@@ -96,28 +97,40 @@ export class Parser {
96
97
  try {
97
98
  // Initialize raw code
98
99
  __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_setRawCode).call(this, rawCode, rawCode);
100
+ timer.push('beforeParse');
101
+ const beforeParsedCode = this.beforeParse(this.rawCode, options);
99
102
  // Override raw code
100
- __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_setRawCode).call(this, this.beforeParse(this.rawCode, options));
103
+ __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_setRawCode).call(this, beforeParsedCode);
101
104
  __classPrivateFieldSet(this, _Parser_authoredElementName, options?.authoredElementName, "f");
102
105
  let frontMatter = null;
103
106
  if (options?.ignoreFrontMatter) {
107
+ timer.push('ignoreFrontMatter');
104
108
  const fm = ignoreFrontMatter(this.rawCode);
105
109
  __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_setRawCode).call(this, fm.code);
106
110
  frontMatter = fm.frontMatter;
107
111
  }
112
+ timer.push('ignoreBlock');
108
113
  const blocks = ignoreBlock(this.rawCode, __classPrivateFieldGet(this, _Parser_ignoreTags, "f"), __classPrivateFieldGet(this, _Parser_maskChar, "f"));
109
114
  __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_setRawCode).call(this, blocks.replaced);
115
+ timer.push('tokenize');
110
116
  const tokenized = this.tokenize(options);
111
117
  const ast = tokenized.ast;
112
118
  const isFragment = tokenized.isFragment;
113
119
  __classPrivateFieldSet(this, _Parser_defaultDepth, options?.depth ?? __classPrivateFieldGet(this, _Parser_defaultDepth, "f"), "f");
120
+ timer.push('traverse');
114
121
  const traversed = this.traverse(ast, null, __classPrivateFieldGet(this, _Parser_defaultDepth, "f"));
122
+ timer.push('afterTraverse');
115
123
  const nodeTree = this.afterTraverse([...traversed.childNodes, ...traversed.siblings]);
124
+ timer.push('flattenNodes');
116
125
  let nodeList = this.flattenNodes(nodeTree);
126
+ timer.push('afterFlattenNodes');
117
127
  nodeList = this.afterFlattenNodes(nodeList);
128
+ timer.push('restoreNode');
118
129
  nodeList = restoreNode(this, nodeList, blocks, false);
130
+ timer.push('afterParse');
119
131
  nodeList = this.afterParse(nodeList, options);
120
132
  if (frontMatter) {
133
+ timer.push('frontMatter');
121
134
  const newNodeList = [...nodeList];
122
135
  let firstText = '';
123
136
  const firstTextNode = newNodeList.shift();
@@ -142,6 +155,7 @@ export class Parser {
142
155
  }
143
156
  nodeList = [fmNode, ...newNodeList];
144
157
  }
158
+ timer.log();
145
159
  domLog(nodeList);
146
160
  __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_reset).call(this);
147
161
  return {
@@ -174,9 +188,22 @@ export class Parser {
174
188
  }
175
189
  const childNodes = [];
176
190
  const siblings = [];
191
+ const existence = new Set();
177
192
  for (const originNode of originNodes) {
193
+ timer.push('nodeize');
178
194
  const nodes = this.nodeize(originNode, parentNode, depth);
179
- const after = this.afterNodeize(nodes, parentNode, depth);
195
+ const filteredNodes = [];
196
+ for (const node of nodes) {
197
+ // Remove duplicated nodes
198
+ const id = `${node.startOffset}:${node.endOffset}:${node.nodeName}:${node.type}:${node.raw}`;
199
+ if (existence.has(id)) {
200
+ continue;
201
+ }
202
+ existence.add(id);
203
+ filteredNodes.push(node);
204
+ }
205
+ timer.push('afterNodeize');
206
+ const after = this.afterNodeize(filteredNodes, parentNode, depth);
180
207
  childNodes.push(...after.siblings);
181
208
  siblings.push(...after.ancestors);
182
209
  }
@@ -186,7 +213,10 @@ export class Parser {
186
213
  };
187
214
  }
188
215
  afterTraverse(nodeTree) {
189
- return __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_siblingsCorrection).call(this, nodeTree);
216
+ return Array.prototype.toSorted == null
217
+ ? // TODO: Use sort instead of toSorted until we end support for Node 18
218
+ [...nodeTree].sort(sortNodes)
219
+ : nodeTree.toSorted(sortNodes);
190
220
  }
191
221
  nodeize(originNode, parentNode, depth) {
192
222
  return [];
@@ -232,6 +262,7 @@ export class Parser {
232
262
  return nodeList;
233
263
  }
234
264
  visitDoctype(token) {
265
+ timer.push('visitDoctype');
235
266
  const node = {
236
267
  ...token,
237
268
  ...this.createToken(token),
@@ -241,6 +272,7 @@ export class Parser {
241
272
  return [node];
242
273
  }
243
274
  visitComment(token, options) {
275
+ timer.push('visitComment');
244
276
  const isBogus = options?.isBogus ?? !token.raw.startsWith('<!--');
245
277
  const node = {
246
278
  ...token,
@@ -252,6 +284,7 @@ export class Parser {
252
284
  return [node];
253
285
  }
254
286
  visitText(token, options) {
287
+ timer.push('visitText');
255
288
  const node = {
256
289
  ...token,
257
290
  ...this.createToken(token),
@@ -270,6 +303,7 @@ export class Parser {
270
303
  return [node];
271
304
  }
272
305
  visitElement(token, childNodes = [], options) {
306
+ timer.push('visitElement');
273
307
  const createEndTagToken = options?.createEndTagToken;
274
308
  const namelessFragment = options?.namelessFragment ?? false;
275
309
  const overwriteProps = options?.overwriteProps;
@@ -311,6 +345,7 @@ export class Parser {
311
345
  return [startTag, ...siblings];
312
346
  }
313
347
  visitPsBlock(token, childNodes = [], conditionalType = null, originBlockNode) {
348
+ timer.push('visitPsBlock');
314
349
  const block = {
315
350
  ...token,
316
351
  ...this.createToken(token),
@@ -335,6 +370,7 @@ export class Parser {
335
370
  return traversed.siblings;
336
371
  }
337
372
  visitSpreadAttr(token) {
373
+ timer.push('visitSpreadAttr');
338
374
  const raw = token.raw.trim();
339
375
  if (raw === '') {
340
376
  return null;
@@ -355,6 +391,7 @@ export class Parser {
355
391
  };
356
392
  }
357
393
  visitAttr(token, options) {
394
+ timer.push('visitAttr');
358
395
  const raw = token.raw;
359
396
  const quoteSet = options?.quoteSet;
360
397
  const startState = options?.startState ?? AttrState.BeforeName;
@@ -587,26 +624,7 @@ export class Parser {
587
624
  };
588
625
  }
589
626
  getOffsetsFromCode(startLine, startCol, endLine, endCol) {
590
- const lines = __classPrivateFieldGet(this, _Parser_rawCode, "f").split('\n');
591
- let offset = 0;
592
- let endOffset = 0;
593
- for (let i = 0; i < startLine - 1; i++) {
594
- const line = lines[i];
595
- if (line == null) {
596
- continue;
597
- }
598
- offset += line.length + 1;
599
- }
600
- offset += startCol - 1;
601
- for (let i = 0; i < endLine - 1; i++) {
602
- const line = lines[i];
603
- if (line == null) {
604
- continue;
605
- }
606
- endOffset += line.length + 1;
607
- }
608
- endOffset += endCol - 1;
609
- return { offset, endOffset };
627
+ return getOffsetsFromCode(this.rawCode, startLine, startCol, endLine, endCol);
610
628
  }
611
629
  walk(nodeList, walker, depth = 0) {
612
630
  for (const node of nodeList) {
@@ -635,7 +653,10 @@ export class Parser {
635
653
  newChildNodes.splice(currentIndex, 1, appendingChild);
636
654
  }
637
655
  Object.assign(parentNode, {
638
- childNodes: __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_siblingsCorrection).call(this, newChildNodes),
656
+ childNodes: Array.prototype.toSorted == null
657
+ ? // TODO: Use sort instead of toSorted until we end support for Node 18
658
+ [...newChildNodes].sort(sortNodes)
659
+ : newChildNodes.toSorted(sortNodes),
639
660
  });
640
661
  }
641
662
  replaceChild(parentNode, oldChildNode, ...replacementChildNodes) {
@@ -792,12 +813,9 @@ _Parser_booleanish = new WeakMap(), _Parser_defaultState = new WeakMap(), _Parse
792
813
  newNodeList.push(node);
793
814
  }
794
815
  return newNodeList;
795
- }, _Parser_pairing = function _Parser_pairing(startTag, endTag, appendChild = true) {
816
+ }, _Parser_pairing = function _Parser_pairing(startTag, endTag) {
796
817
  Object.assign(startTag, { pairNode: endTag });
797
818
  Object.assign(endTag, { pairNode: startTag });
798
- if (!appendChild) {
799
- return;
800
- }
801
819
  this.appendChild(startTag.parentNode, endTag);
802
820
  }, _Parser_parseEndTag = function _Parser_parseEndTag(token, namelessFragment) {
803
821
  const parsed = __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_parseTag).call(this, token, true, false, namelessFragment);
@@ -1080,34 +1098,6 @@ _Parser_booleanish = new WeakMap(), _Parser_defaultState = new WeakMap(), _Parse
1080
1098
  }, _Parser_setRawCode = function _Parser_setRawCode(rawCode, originalRawCode) {
1081
1099
  __classPrivateFieldSet(this, _Parser_rawCode, rawCode, "f");
1082
1100
  __classPrivateFieldSet(this, _Parser_originalRawCode, originalRawCode ?? __classPrivateFieldGet(this, _Parser_originalRawCode, "f"), "f");
1083
- }, _Parser_siblingsCorrection = function _Parser_siblingsCorrection(nodes) {
1084
- const stack = new Set();
1085
- const newNodes = [];
1086
- const oldNodes = Array.prototype.toSorted == null
1087
- ? // TODO: Use sort instead of toSorted until we end support for Node 18
1088
- [...nodes].sort(sortNodes)
1089
- : nodes.toSorted(sortNodes);
1090
- const nameToLastOpenTag = {};
1091
- for (const node of oldNodes) {
1092
- const id = `${node.startOffset}::${node.nodeName}`;
1093
- if (stack.has(id)) {
1094
- continue;
1095
- }
1096
- stack.add(id);
1097
- if (node.type === 'endtag') {
1098
- const openTag = nameToLastOpenTag[node.nodeName];
1099
- if (openTag && !openTag.pairNode) {
1100
- __classPrivateFieldGet(this, _Parser_instances, "m", _Parser_pairing).call(this, openTag, node, false);
1101
- newNodes.push(node);
1102
- continue;
1103
- }
1104
- }
1105
- else if (node.type === 'starttag') {
1106
- nameToLastOpenTag[node.nodeName] = node;
1107
- }
1108
- newNodes.push(node);
1109
- }
1110
- return newNodes;
1111
1101
  }, _Parser_trimText = function _Parser_trimText(nodeList) {
1112
1102
  const newNodeList = [];
1113
1103
  let prevNode = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/parser-utils",
3
- "version": "4.6.8",
3
+ "version": "4.7.1",
4
4
  "description": "Utility module for markuplint parser plugin",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -28,9 +28,9 @@
28
28
  "clean": "tsc --build --clean"
29
29
  },
30
30
  "dependencies": {
31
- "@markuplint/ml-ast": "4.4.5",
32
- "@markuplint/ml-spec": "4.7.1",
33
- "@markuplint/types": "4.6.1",
31
+ "@markuplint/ml-ast": "4.4.7",
32
+ "@markuplint/ml-spec": "4.8.0",
33
+ "@markuplint/types": "4.6.3",
34
34
  "@types/uuid": "10.0.0",
35
35
  "debug": "4.3.7",
36
36
  "espree": "10.2.0",
@@ -38,7 +38,7 @@
38
38
  "uuid": "10.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@typescript-eslint/typescript-estree": "8.8.1"
41
+ "@typescript-eslint/typescript-estree": "8.11.0"
42
42
  },
43
- "gitHead": "e59d4e8b762c66c7e3fb8b0a0d9d99d5160b0afa"
43
+ "gitHead": "fab5b494f0bdc491aa83cb2c8722738d557fbefd"
44
44
  }