@markuplint/parser-utils 4.0.0-dev.28 → 4.0.0-rc.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.
Files changed (45) hide show
  1. package/LICENSE +1 -1
  2. package/lib/attr-tokenizer.d.ts +18 -0
  3. package/lib/attr-tokenizer.js +169 -0
  4. package/lib/const.d.ts +8 -1
  5. package/lib/const.js +9 -3
  6. package/lib/debug.d.ts +4 -0
  7. package/lib/debug.js +6 -0
  8. package/lib/debugger.d.ts +3 -2
  9. package/lib/debugger.js +41 -19
  10. package/lib/enums.d.ts +16 -0
  11. package/lib/enums.js +18 -0
  12. package/lib/get-location.d.ts +4 -13
  13. package/lib/get-location.js +10 -21
  14. package/lib/ignore-block.d.ts +3 -2
  15. package/lib/ignore-block.js +68 -118
  16. package/lib/ignore-front-matter.d.ts +4 -1
  17. package/lib/ignore-front-matter.js +13 -4
  18. package/lib/index.d.ts +4 -13
  19. package/lib/index.js +4 -13
  20. package/lib/parser-error.d.ts +1 -0
  21. package/lib/parser-error.js +1 -0
  22. package/lib/parser.d.ts +112 -0
  23. package/lib/parser.js +1120 -0
  24. package/lib/script-parser.d.ts +5 -0
  25. package/lib/script-parser.js +12 -0
  26. package/lib/sort-nodes.d.ts +2 -0
  27. package/lib/sort-nodes.js +18 -0
  28. package/lib/types.d.ts +40 -2
  29. package/package.json +12 -6
  30. package/lib/create-token.d.ts +0 -4
  31. package/lib/create-token.js +0 -29
  32. package/lib/flatten-nodes.d.ts +0 -2
  33. package/lib/flatten-nodes.js +0 -247
  34. package/lib/get-space-before.d.ts +0 -1
  35. package/lib/get-space-before.js +0 -8
  36. package/lib/parse-attr.d.ts +0 -24
  37. package/lib/parse-attr.js +0 -144
  38. package/lib/remove-deprecated-node.d.ts +0 -7
  39. package/lib/remove-deprecated-node.js +0 -39
  40. package/lib/siblings-correction.d.ts +0 -9
  41. package/lib/siblings-correction.js +0 -21
  42. package/lib/tag-splitter.d.ts +0 -7
  43. package/lib/tag-splitter.js +0 -96
  44. package/lib/walker.d.ts +0 -2
  45. package/lib/walker.js +0 -18
@@ -1,21 +1,10 @@
1
1
  import { MASK_CHAR } from './const.js';
2
- import { uuid } from './create-token.js';
3
- import { sliceFragment } from './get-location.js';
4
- import { siblingsCorrection } from './siblings-correction.js';
2
+ import { getCol, getLine } from './get-location.js';
3
+ import { ParserError } from './parser-error.js';
5
4
  export function ignoreBlock(source, tags, maskChar = MASK_CHAR) {
6
5
  let replaced = source;
7
6
  const stack = [];
8
7
  for (const tag of tags) {
9
- // Replace tags in attributes
10
- const attr = maskText(prepend(tag.start, '(?<=(?:"|\'))'), append(tag.end, '(?=(?:"|\'))'), replaced, (startTag, taggedCode, endTag) => {
11
- const mask = maskChar.repeat(startTag.length) +
12
- taggedCode.replaceAll(/[^\n]/g, maskChar) +
13
- maskChar.repeat((endTag ?? '').length);
14
- return mask;
15
- });
16
- replaced = attr.replaced;
17
- stack.push(...attr.stack.map(res => ({ ...res, type: tag.type })));
18
- // Replace tags in other nodes
19
8
  const text = maskText(tag.start, tag.end, replaced, (startTag, taggedCode, endTag) => {
20
9
  const mask = maskChar.repeat(startTag.length) +
21
10
  taggedCode.replaceAll(/[^\n]/g, maskChar) +
@@ -49,6 +38,7 @@ function maskText(start, end, replaced, masking) {
49
38
  startTag,
50
39
  taggedCode,
51
40
  endTag: endTag ?? null,
41
+ resolved: false,
52
42
  });
53
43
  /**
54
44
  * It will not replace line breaks because detects line number.
@@ -62,106 +52,41 @@ function maskText(start, end, replaced, masking) {
62
52
  }
63
53
  export function restoreNode(
64
54
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
65
- nodeList, ignoreBlock) {
66
- nodeList = [...nodeList];
55
+ parser, nodeList,
56
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
57
+ ignoreBlock, throwErrorWhenTagHasUnresolved = true) {
58
+ const newNodeList = [...nodeList];
67
59
  const { source, stack, maskChar } = ignoreBlock;
68
- for (const node of nodeList) {
69
- if (node.type === 'comment' || node.type === 'text' || node.type === 'psblock') {
70
- if (!hasIgnoreBlock(node.raw, maskChar)) {
71
- continue;
72
- }
73
- const parentNode = node.parentNode;
74
- const index = nodeList.indexOf(node);
75
- const insertList = [];
76
- let text = node.raw;
77
- let pointer = 0;
78
- for (const tag of stack) {
79
- if (node.startOffset <= tag.index && tag.index < node.endOffset) {
80
- const start = tag.index - node.startOffset;
81
- const body = tag.startTag + tag.taggedCode + (tag.endTag ?? '');
82
- const above = node.raw.slice(pointer, start);
83
- const below = text.slice(above.length + body.length);
84
- if (above) {
85
- const offset = node.startOffset + pointer;
86
- const { raw, startOffset, endOffset, startLine, endLine, startCol, endCol } = sliceFragment(source, offset, offset + above.length);
87
- const textNode = {
88
- ...node,
89
- uuid: uuid(),
90
- type: 'text',
91
- raw,
92
- startOffset,
93
- endOffset,
94
- startLine,
95
- endLine,
96
- startCol,
97
- endCol,
98
- };
99
- if (node.prevNode?.nextNode) {
100
- node.prevNode.nextNode = textNode;
101
- }
102
- if (node.nextNode?.prevNode) {
103
- node.nextNode.prevNode = textNode;
104
- }
105
- insertList.push(textNode);
106
- }
107
- if (body) {
108
- const offset = node.startOffset + pointer + above.length;
109
- const { raw, startOffset, endOffset, startLine, endLine, startCol, endCol } = sliceFragment(source, offset, offset + body.length);
110
- const bodyNode = {
111
- uuid: uuid(),
112
- type: 'psblock',
113
- nodeName: `#ps:${tag.type}`,
114
- raw,
115
- parentNode: node.parentNode,
116
- prevNode: null,
117
- nextNode: null,
118
- isFragment: node.isFragment,
119
- isGhost: false,
120
- startOffset,
121
- endOffset,
122
- startLine,
123
- endLine,
124
- startCol,
125
- endCol,
126
- };
127
- if (node.prevNode?.nextNode) {
128
- node.prevNode.nextNode = bodyNode;
129
- }
130
- if (node.nextNode?.prevNode) {
131
- node.nextNode.prevNode = bodyNode;
132
- }
133
- insertList.push(bodyNode);
134
- }
135
- text = below;
136
- pointer = start + body.length;
137
- }
138
- }
139
- if (text) {
140
- const offset = node.endOffset - text.length;
141
- const { raw, startOffset, endOffset, startLine, endLine, startCol, endCol } = sliceFragment(source, offset, offset + text.length);
142
- const textNode = {
143
- ...node,
144
- uuid: uuid(),
145
- type: 'text',
146
- raw,
147
- startOffset,
148
- endOffset,
149
- startLine,
150
- endLine,
151
- startCol,
152
- endCol,
153
- };
154
- insertList.push(textNode);
155
- }
156
- siblingsCorrection(insertList);
157
- if (parentNode) {
158
- parentNode.childNodes = insertList;
159
- }
160
- nodeList.splice(index, 1, ...insertList);
60
+ if (stack.length === 0) {
61
+ return newNodeList;
62
+ }
63
+ for (const tag of stack) {
64
+ const node = newNodeList.find(node => node.startOffset === tag.index);
65
+ if (!node) {
66
+ continue;
67
+ }
68
+ const raw = `${tag.startTag}${tag.taggedCode}${tag.endTag ?? ''}`;
69
+ const token = parser.createToken(raw, node.startOffset, node.startLine, node.startCol);
70
+ const psNode = {
71
+ ...token,
72
+ type: 'psblock',
73
+ depth: node.depth,
74
+ nodeName: `#ps:${tag.type}`,
75
+ parentNode: node.parentNode,
76
+ childNodes: [],
77
+ isBogus: false,
78
+ };
79
+ if (node.type !== 'doctype' && node.parentNode?.childNodes) {
80
+ parser.replaceChild(node.parentNode, node, psNode);
161
81
  }
82
+ const index = newNodeList.indexOf(node);
83
+ newNodeList.splice(index, 1, psNode);
84
+ tag.resolved = true;
85
+ }
86
+ for (const node of newNodeList) {
162
87
  if (node.type === 'starttag') {
163
88
  for (const attr of node.attributes) {
164
- if (attr.type === 'ps-attr' || attr.value.raw === '' || !hasIgnoreBlock(attr.value.raw, maskChar)) {
89
+ if (attr.type === 'spread' || attr.value.raw === '' || !hasIgnoreBlock(attr.value.raw, maskChar)) {
165
90
  continue;
166
91
  }
167
92
  for (const tag of stack) {
@@ -171,14 +96,39 @@ nodeList, ignoreBlock) {
171
96
  const offset = tag.index - attr.value.startOffset;
172
97
  const above = attr.value.raw.slice(0, offset);
173
98
  const below = attr.value.raw.slice(offset + length);
174
- attr.value.raw = above + raw + below;
175
- attr.isDynamicValue = true;
99
+ parser.updateRaw(attr.value, above + raw + below);
100
+ parser.updateAttr(attr, { isDynamicValue: true });
101
+ tag.resolved = true;
176
102
  }
103
+ parser.updateRaw(attr, attr.name.raw +
104
+ attr.spacesBeforeEqual.raw +
105
+ attr.equal.raw +
106
+ attr.spacesAfterEqual.raw +
107
+ attr.startQuote.raw +
108
+ attr.value.raw +
109
+ attr.endQuote.raw);
177
110
  }
111
+ // Update node raw
112
+ const length = attr.raw.length;
113
+ const offset = attr.startOffset - node.startOffset;
114
+ const above = node.raw.slice(0, offset);
115
+ const below = node.raw.slice(offset + length);
116
+ parser.updateRaw(node, above + attr.raw + below);
178
117
  }
179
118
  }
180
119
  }
181
- return nodeList;
120
+ if (throwErrorWhenTagHasUnresolved) {
121
+ for (const tag of stack) {
122
+ if (!tag.resolved) {
123
+ throw new ParserError('Parsing failed. Unsupported syntax detected', {
124
+ line: getLine(source, tag.index),
125
+ col: getCol(source, tag.index),
126
+ raw: tag.startTag + tag.taggedCode + (tag.endTag ?? ''),
127
+ });
128
+ }
129
+ }
130
+ }
131
+ return newNodeList;
182
132
  }
183
133
  function snap(str, reg) {
184
134
  const matched = reg.exec(str);
@@ -192,14 +142,14 @@ function snap(str, reg) {
192
142
  return [index, above, snapPoint, below];
193
143
  }
194
144
  function removeGlobalOption(reg) {
145
+ if (typeof reg === 'string') {
146
+ return new RegExp(escapeRegExpForStr(reg));
147
+ }
195
148
  return new RegExp(reg.source, reg.ignoreCase ? 'i' : '');
196
149
  }
197
- function prepend(reg, str) {
198
- return new RegExp(str + reg.source, reg.ignoreCase ? 'i' : '');
199
- }
200
- function append(reg, str) {
201
- return new RegExp(reg.source + str, reg.ignoreCase ? 'i' : '');
202
- }
203
150
  function hasIgnoreBlock(textContent, maskChar) {
204
151
  return textContent.includes(maskChar);
205
152
  }
153
+ function escapeRegExpForStr(str) {
154
+ return str.replaceAll(/[!$()*+./:=?[\\\]^{|}]/g, '\\$&');
155
+ }
@@ -1 +1,4 @@
1
- export declare function ignoreFrontMatter(code: string): string;
1
+ export declare function ignoreFrontMatter(code: string): {
2
+ code: string;
3
+ frontMatter: string | null;
4
+ };
@@ -1,17 +1,26 @@
1
1
  export function ignoreFrontMatter(code) {
2
- const reStart = /^(?:\s*\r?\n)?---\r?\n/.exec(code);
2
+ const reStart = /^(?:\s*\n)?---\r?\n/.exec(code);
3
3
  if (!reStart) {
4
- return code;
4
+ return {
5
+ code,
6
+ frontMatter: null,
7
+ };
5
8
  }
6
9
  const startPoint = reStart[0].length;
7
10
  const afterStart = code.slice(startPoint);
8
11
  const reEnd = /\r?\n---\r?\n/.exec(afterStart);
9
12
  if (!reEnd) {
10
- return code;
13
+ return {
14
+ code,
15
+ frontMatter: null,
16
+ };
11
17
  }
12
18
  const endPoint = startPoint + reEnd.index + reEnd[0].length;
13
19
  const frontMatter = code.slice(0, endPoint);
14
20
  const afterCode = code.slice(endPoint);
15
21
  const masked = frontMatter.replaceAll(/[^\n\r]/g, ' ');
16
- return masked + afterCode;
22
+ return {
23
+ code: masked + afterCode,
24
+ frontMatter,
25
+ };
17
26
  }
package/lib/index.d.ts CHANGED
@@ -1,16 +1,7 @@
1
- export * from './const.js';
2
- export * from './create-token.js';
3
1
  export * from './debugger.js';
4
- export * from './decision.js';
5
- export * from './detect-element-type.js';
6
- export * from './flatten-nodes.js';
7
- export * from './get-location.js';
8
- export * from './get-space-before.js';
2
+ export * from './enums.js';
9
3
  export * from './idl-attributes.js';
10
- export * from './ignore-block.js';
11
- export * from './ignore-front-matter.js';
12
- export * from './parse-attr.js';
13
4
  export * from './parser-error.js';
14
- export * from './remove-deprecated-node.js';
15
- export * from './tag-splitter.js';
16
- export * from './walker.js';
5
+ export * from './parser.js';
6
+ export * from './script-parser.js';
7
+ export * from './types.js';
package/lib/index.js CHANGED
@@ -1,16 +1,7 @@
1
- export * from './const.js';
2
- export * from './create-token.js';
3
1
  export * from './debugger.js';
4
- export * from './decision.js';
5
- export * from './detect-element-type.js';
6
- export * from './flatten-nodes.js';
7
- export * from './get-location.js';
8
- export * from './get-space-before.js';
2
+ export * from './enums.js';
9
3
  export * from './idl-attributes.js';
10
- export * from './ignore-block.js';
11
- export * from './ignore-front-matter.js';
12
- export * from './parse-attr.js';
13
4
  export * from './parser-error.js';
14
- export * from './remove-deprecated-node.js';
15
- export * from './tag-splitter.js';
16
- export * from './walker.js';
5
+ export * from './parser.js';
6
+ export * from './script-parser.js';
7
+ export * from './types.js';
@@ -2,6 +2,7 @@ export type ParserErrorInfo = {
2
2
  readonly line?: number;
3
3
  readonly col?: number;
4
4
  readonly raw?: string;
5
+ readonly stack?: string;
5
6
  };
6
7
  export declare class ParserError extends Error {
7
8
  readonly col: number;
@@ -5,6 +5,7 @@ export class ParserError extends Error {
5
5
  this.line = info.line ?? 1;
6
6
  this.col = info.col ?? 0;
7
7
  this.raw = info.raw ?? '';
8
+ this.stack = info.stack ?? this.stack;
8
9
  }
9
10
  }
10
11
  export class TargetParserError extends ParserError {
@@ -0,0 +1,112 @@
1
+ import type { Token, ChildToken, QuoteSet, ParseOptions, ParserOptions, Tokenized } from './types.js';
2
+ import type { EndTagType, MLASTDocument, MLASTParentNode, MLParser, ParserAuthoredElementNameDistinguishing, MLASTElement, MLASTElementCloseTag, MLASTToken, MLASTNodeTreeItem, MLASTTag, MLASTText, MLASTAttr, MLASTChildNode, MLASTSpreadAttr, ElementType, Walker, MLASTHTMLAttr } from '@markuplint/ml-ast';
3
+ import { AttrState } from './enums.js';
4
+ import { ParserError } from './parser-error.js';
5
+ export declare abstract class Parser<Node extends {} = {}, State extends unknown = null> implements MLParser {
6
+ #private;
7
+ state: State;
8
+ get authoredElementName(): ParserAuthoredElementNameDistinguishing | undefined;
9
+ /**
10
+ * Detect value as a true if its attribute is booleanish value and omitted.
11
+ *
12
+ * Ex:
13
+ * ```jsx
14
+ * <Component aria-hidden />
15
+ * ```
16
+ *
17
+ * In the above, the `aria-hidden` is `true`.
18
+ */
19
+ get booleanish(): boolean;
20
+ /**
21
+ * The end tag omittable type.
22
+ *
23
+ * - `"xml"`: Must need an end tag or must self-close
24
+ * - `"omittable"`: May omit
25
+ * - `"never"`: Never need
26
+ */
27
+ get endTag(): EndTagType;
28
+ get rawCode(): string;
29
+ get tagNameCaseSensitive(): boolean;
30
+ constructor(options?: ParserOptions, defaultState?: State);
31
+ tokenize(options?: ParseOptions): Tokenized<Node, State>;
32
+ beforeParse(rawCode: string, options?: ParseOptions): string;
33
+ parse(rawCode: string, options?: ParseOptions): MLASTDocument;
34
+ afterParse(nodeList: readonly MLASTNodeTreeItem[], options?: ParseOptions): readonly MLASTNodeTreeItem[];
35
+ parseError(error: any): ParserError;
36
+ traverse(originNodes: readonly Node[], parentNode: MLASTParentNode | null | undefined, depth: number): {
37
+ childNodes: readonly MLASTChildNode[];
38
+ siblings: readonly MLASTNodeTreeItem[];
39
+ };
40
+ afterTraverse(nodeTree: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
41
+ nodeize(originNode: Node, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
42
+ afterNodeize(siblings: readonly MLASTNodeTreeItem[], parentNode: MLASTParentNode | null, depth: number): {
43
+ siblings: MLASTChildNode[];
44
+ ancestors: MLASTNodeTreeItem[];
45
+ };
46
+ flattenNodes(nodeTree: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
47
+ afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[], options?: {
48
+ readonly exposeInvalidNode?: boolean;
49
+ readonly exposeWhiteSpace?: boolean;
50
+ readonly concatText?: boolean;
51
+ }): readonly MLASTNodeTreeItem[];
52
+ visitDoctype(token: ChildToken & {
53
+ readonly name: string;
54
+ readonly publicId: string;
55
+ readonly systemId: string;
56
+ }): readonly MLASTNodeTreeItem[];
57
+ visitComment(token: ChildToken, options?: {
58
+ readonly isBogus?: boolean;
59
+ }): readonly MLASTNodeTreeItem[];
60
+ visitText(token: ChildToken, options?: {
61
+ readonly researchTags?: boolean;
62
+ readonly invalidTagAsText?: boolean;
63
+ }): readonly MLASTNodeTreeItem[];
64
+ visitElement(token: ChildToken & {
65
+ readonly nodeName: string;
66
+ readonly namespace: string;
67
+ }, childNodes?: readonly Node[], options?: {
68
+ readonly createEndTagToken?: (startTag: MLASTElement) => ChildToken | null;
69
+ readonly namelessFragment?: boolean;
70
+ readonly overwriteProps?: Partial<MLASTElement>;
71
+ }): readonly MLASTNodeTreeItem[];
72
+ visitPsBlock(token: ChildToken & {
73
+ readonly nodeName: string;
74
+ }, childNodes?: readonly Node[], originBlockNode?: Node): readonly MLASTNodeTreeItem[];
75
+ visitChildren(children: readonly Node[], parentNode: MLASTParentNode | null): readonly MLASTNodeTreeItem[];
76
+ visitSpreadAttr(token: Token): MLASTSpreadAttr | null;
77
+ visitAttr(token: Token, options?: {
78
+ readonly quoteSet?: readonly QuoteSet[];
79
+ readonly quoteInValueChars?: readonly QuoteSet[];
80
+ readonly endOfUnquotedValueChars?: readonly string[];
81
+ readonly startState?: AttrState;
82
+ }): MLASTAttr & {
83
+ __rightText?: string;
84
+ };
85
+ parseCodeFragment(token: ChildToken, options?: {
86
+ readonly namelessFragment?: boolean;
87
+ }): (MLASTTag | MLASTText)[];
88
+ updateLocation(node: MLASTNodeTreeItem, props: Partial<Pick<MLASTNodeTreeItem, 'startOffset' | 'startLine' | 'startCol' | 'depth'>>): void;
89
+ /**
90
+ * Set new raw code to target node.
91
+ *
92
+ * Replace the raw code and update the start/end offset/line/column.
93
+ *
94
+ * @param node target node
95
+ * @param raw new raw code
96
+ */
97
+ updateRaw(node: MLASTToken, raw: string): void;
98
+ updateElement(el: MLASTElement, props: Partial<Pick<MLASTElement, 'nodeName' | 'elementType'>>): void;
99
+ updateElement(el: MLASTElementCloseTag, props: Partial<Pick<MLASTElementCloseTag, 'nodeName'>>): void;
100
+ updateAttr(attr: MLASTHTMLAttr, props: Partial<Pick<MLASTHTMLAttr, 'isDynamicValue' | 'isDirective' | 'potentialName' | 'potentialValue' | 'valueType' | 'candidate' | 'isDuplicatable'>>): void;
101
+ detectElementType(nodeName: string, defaultPattern?: ParserAuthoredElementNameDistinguishing): ElementType;
102
+ createToken(token: Token): MLASTToken;
103
+ createToken(token: string, startOffset: number, startLine: number, startCol: number): MLASTToken;
104
+ sliceFragment(start: number, end?: number): Token;
105
+ getOffsetsFromCode(startLine: number, startCol: number, endLine: number, endCol: number): {
106
+ offset: number;
107
+ endOffset: number;
108
+ };
109
+ walk<Node extends MLASTNodeTreeItem>(nodeList: readonly Node[], walker: Walker<Node>, depth?: number): void;
110
+ appendChild(parentNode: MLASTParentNode | null, ...childNodes: readonly MLASTChildNode[]): void;
111
+ replaceChild(parentNode: MLASTParentNode, oldChildNode: MLASTChildNode, newChildNode: MLASTChildNode): void;
112
+ }