@markuplint/pug-parser 4.0.0-dev.23 → 4.0.0-rc.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.
- package/lib/parser.d.ts +3 -7
- package/lib/parser.js +132 -25
- package/lib/pug-parser/index.d.ts +16 -4
- package/lib/pug-parser/index.js +37 -3
- package/package.json +5 -5
package/lib/parser.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ASTNode } from './pug-parser/index.js';
|
|
2
2
|
import type { MLASTAttr, MLASTNodeTreeItem, MLASTParentNode } from '@markuplint/ml-ast';
|
|
3
|
-
import type { ChildToken, Token } from '@markuplint/parser-utils';
|
|
3
|
+
import type { ChildToken, ParseOptions, Token } from '@markuplint/parser-utils';
|
|
4
4
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
5
5
|
declare class PugParser extends Parser<ASTNode> {
|
|
6
6
|
constructor();
|
|
7
|
-
tokenize(): {
|
|
7
|
+
tokenize(options?: ParseOptions): {
|
|
8
8
|
ast: ASTNode[];
|
|
9
9
|
isFragment: boolean;
|
|
10
10
|
};
|
|
@@ -19,11 +19,7 @@ declare class PugParser extends Parser<ASTNode> {
|
|
|
19
19
|
readonly attributes: readonly MLASTAttr[];
|
|
20
20
|
};
|
|
21
21
|
}): MLASTNodeTreeItem[];
|
|
22
|
-
visitAttr(token: Token):
|
|
23
|
-
__rightText?: string | undefined;
|
|
24
|
-
}) | (import("@markuplint/ml-ast").MLASTSpreadAttr & {
|
|
25
|
-
__rightText?: string | undefined;
|
|
26
|
-
});
|
|
22
|
+
visitAttr(token: Token): MLASTAttr;
|
|
27
23
|
}
|
|
28
24
|
export declare const parser: PugParser;
|
|
29
25
|
export {};
|
package/lib/parser.js
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ParserError, Parser, AttrState,
|
|
1
|
+
import { HtmlParser, getNamespace } from '@markuplint/html-parser';
|
|
2
|
+
import { ParserError, Parser, AttrState, scriptParser } from '@markuplint/parser-utils';
|
|
3
3
|
import { pugParse } from './pug-parser/index.js';
|
|
4
|
+
class HtmlInPugParser extends HtmlParser {
|
|
5
|
+
constructor() {
|
|
6
|
+
super({
|
|
7
|
+
ignoreTags: [
|
|
8
|
+
/**
|
|
9
|
+
* Tag Interpolation
|
|
10
|
+
*
|
|
11
|
+
* @see https://pugjs.org/language/interpolation.html#tag-interpolation
|
|
12
|
+
*/
|
|
13
|
+
{
|
|
14
|
+
type: 'tag-interpolation',
|
|
15
|
+
start: '#[',
|
|
16
|
+
end: ']',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
4
22
|
class PugParser extends Parser {
|
|
5
23
|
constructor() {
|
|
6
24
|
super({
|
|
7
25
|
endTagType: 'never',
|
|
8
26
|
});
|
|
9
27
|
}
|
|
10
|
-
tokenize() {
|
|
28
|
+
tokenize(options) {
|
|
29
|
+
const offsetOffset = options?.offsetOffset ?? 0;
|
|
11
30
|
return {
|
|
12
|
-
ast: pugParse(this.rawCode).nodes,
|
|
31
|
+
ast: pugParse(this.rawCode, offsetOffset >= 1).nodes,
|
|
13
32
|
isFragment: true,
|
|
14
33
|
};
|
|
15
34
|
}
|
|
@@ -43,13 +62,29 @@ class PugParser extends Parser {
|
|
|
43
62
|
if (originNode.raw.trim() === '') {
|
|
44
63
|
return [];
|
|
45
64
|
}
|
|
46
|
-
const htmlDoc =
|
|
65
|
+
const htmlDoc = new HtmlInPugParser().parse(originNode.raw, {
|
|
47
66
|
offsetOffset: originNode.offset,
|
|
48
67
|
offsetLine: originNode.line,
|
|
49
68
|
offsetColumn: originNode.column,
|
|
50
69
|
depth,
|
|
51
70
|
});
|
|
52
|
-
|
|
71
|
+
const newNodeList = [];
|
|
72
|
+
for (const node of htmlDoc.nodeList) {
|
|
73
|
+
if (node.nodeName === '#ps:tag-interpolation') {
|
|
74
|
+
// Remove `#[` and `]`
|
|
75
|
+
const raw = node.raw.slice(2, -1);
|
|
76
|
+
const innerNodes = new PugParser().parse(raw, {
|
|
77
|
+
offsetOffset: node.startOffset + 2,
|
|
78
|
+
offsetLine: node.startLine,
|
|
79
|
+
offsetColumn: node.startCol + 2,
|
|
80
|
+
depth: node.depth,
|
|
81
|
+
});
|
|
82
|
+
newNodeList.push(...innerNodes.nodeList);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
newNodeList.push(node);
|
|
86
|
+
}
|
|
87
|
+
return newNodeList;
|
|
53
88
|
}
|
|
54
89
|
case 'Comment': {
|
|
55
90
|
return this.visitComment({
|
|
@@ -62,6 +97,37 @@ class PugParser extends Parser {
|
|
|
62
97
|
}
|
|
63
98
|
case 'Tag': {
|
|
64
99
|
const namespace = getNamespace(originNode.name, parentNamespace);
|
|
100
|
+
const attrs = originNode.attrs.map(attr => {
|
|
101
|
+
// eslint-disable-next-line prefer-const
|
|
102
|
+
let { offset, endOffset } = this.getOffsetsFromCode(attr.line, attr.column, attr.endLine, attr.endColumn);
|
|
103
|
+
if ((attr.name === 'id' || attr.name === 'class') &&
|
|
104
|
+
attr.offset === attr.endOffset &&
|
|
105
|
+
typeof attr.val === 'string') {
|
|
106
|
+
/**
|
|
107
|
+
* #value =>
|
|
108
|
+
* {
|
|
109
|
+
* name: 'id',
|
|
110
|
+
* val: "'value'",
|
|
111
|
+
* }
|
|
112
|
+
* Remove single quotes and add (#|.) prefix
|
|
113
|
+
*/
|
|
114
|
+
endOffset = attr.offset + attr.val.length - 1;
|
|
115
|
+
}
|
|
116
|
+
const token = this.sliceFragment(offset, endOffset);
|
|
117
|
+
return this.visitAttr(token);
|
|
118
|
+
});
|
|
119
|
+
// &attributes(syntax)
|
|
120
|
+
const andAttr = originNode.attributeBlocks.map(block => {
|
|
121
|
+
const blockLength = '&attributes('.length;
|
|
122
|
+
const { offset, endOffset } = this.getOffsetsFromCode(block.line, block.column + blockLength, block.line, block.column + blockLength + block.val.length);
|
|
123
|
+
const token = this.sliceFragment(offset, endOffset);
|
|
124
|
+
const node = this.createToken(token.raw, token.startOffset, token.startLine, token.startCol);
|
|
125
|
+
return {
|
|
126
|
+
...node,
|
|
127
|
+
type: 'spread',
|
|
128
|
+
nodeName: '#spread',
|
|
129
|
+
};
|
|
130
|
+
});
|
|
65
131
|
return this.visitElement({
|
|
66
132
|
...token,
|
|
67
133
|
depth,
|
|
@@ -70,10 +136,7 @@ class PugParser extends Parser {
|
|
|
70
136
|
namespace,
|
|
71
137
|
}, originNode.block.nodes, {
|
|
72
138
|
overwriteProps: {
|
|
73
|
-
attributes:
|
|
74
|
-
const token = this.sliceFragment(attr.offset, attr.endOffset);
|
|
75
|
-
return this.visitAttr(token);
|
|
76
|
-
}),
|
|
139
|
+
attributes: [...attrs, ...andAttr],
|
|
77
140
|
},
|
|
78
141
|
});
|
|
79
142
|
}
|
|
@@ -122,10 +185,11 @@ class PugParser extends Parser {
|
|
|
122
185
|
if (attr.type === 'spread') {
|
|
123
186
|
return attr;
|
|
124
187
|
}
|
|
188
|
+
const potentialName = token.raw[0] === '#' ? 'id' : 'class';
|
|
125
189
|
this.updateAttr(attr, {
|
|
126
|
-
potentialName
|
|
127
|
-
potentialValue: attr.
|
|
128
|
-
isDuplicatable:
|
|
190
|
+
potentialName,
|
|
191
|
+
potentialValue: attr.raw.slice(1),
|
|
192
|
+
isDuplicatable: potentialName === 'class',
|
|
129
193
|
});
|
|
130
194
|
return attr;
|
|
131
195
|
}
|
|
@@ -140,23 +204,66 @@ class PugParser extends Parser {
|
|
|
140
204
|
if (attr.name.raw.toLowerCase() === 'class') {
|
|
141
205
|
this.updateAttr(attr, { isDuplicatable: true });
|
|
142
206
|
}
|
|
207
|
+
if (attr.name.raw.startsWith("'") && attr.name.raw.endsWith("'")) {
|
|
208
|
+
this.updateAttr(attr, {
|
|
209
|
+
potentialName: attr.name.raw.slice(1, -1),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (attr.name.raw.endsWith('!')) {
|
|
213
|
+
this.updateAttr(attr, {
|
|
214
|
+
potentialName: attr.name.raw.slice(0, -1),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
143
217
|
const valueCodeTokens = scriptParser(attr.value.raw.trim());
|
|
144
218
|
if (valueCodeTokens.length === 1) {
|
|
145
219
|
const token = valueCodeTokens[0];
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
220
|
+
switch (token.type) {
|
|
221
|
+
case 'Numeric': {
|
|
222
|
+
return {
|
|
223
|
+
...attr,
|
|
224
|
+
valueType: 'number',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
case 'Boolean': {
|
|
228
|
+
return {
|
|
229
|
+
...attr,
|
|
230
|
+
valueType: 'boolean',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case 'String':
|
|
234
|
+
case 'Template': {
|
|
235
|
+
const value = super.visitAttr(attr.value, {
|
|
236
|
+
startState: AttrState.BeforeValue,
|
|
237
|
+
quoteSet: [
|
|
238
|
+
{ start: '"', end: '"' },
|
|
239
|
+
{ start: "'", end: "'" },
|
|
240
|
+
{ start: '`', end: '`' },
|
|
241
|
+
],
|
|
242
|
+
quoteInValueChars: [
|
|
243
|
+
{ start: '"', end: '"' },
|
|
244
|
+
{ start: "'", end: "'" },
|
|
245
|
+
{ start: '`', end: '`' },
|
|
246
|
+
{ start: '${', end: '}' },
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
if (value.type === 'spread') {
|
|
250
|
+
throw new ParserError('Unexpected attribute value', value);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
...attr,
|
|
254
|
+
startQuote: value.startQuote,
|
|
255
|
+
value: value.value,
|
|
256
|
+
endQuote: value.endQuote,
|
|
257
|
+
...(attr.name.raw.endsWith('!') ? { valueType: 'code' } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
154
260
|
}
|
|
155
261
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
262
|
+
return {
|
|
263
|
+
...attr,
|
|
264
|
+
isDynamicValue: true,
|
|
265
|
+
valueType: 'code',
|
|
266
|
+
};
|
|
160
267
|
}
|
|
161
268
|
}
|
|
162
269
|
export const parser = new PugParser();
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export declare function pugParse(pug: string): ASTBlock;
|
|
1
|
+
export declare function pugParse(pug: string, useOffset?: boolean): ASTBlock;
|
|
2
2
|
export type ASTBlock = PugAST<ASTNode>;
|
|
3
|
-
export type ASTNode = ASTTagNode | ASTTextNode | ASTCodeNode | ASTComment | ASTDoctype | ASTIncludeNode | ASTMixinNode | ASTMixinSlotNode | ASTFilterNode | ASTEachNode | ASTConditionalNode | ASTCaseNode | ASTCaseWhenNode;
|
|
4
|
-
export type ASTTagNode = Omit<PugASTTagNode<ASTAttr, ASTBlock>, '
|
|
3
|
+
export type ASTNode = ASTTagNode | ASTTextNode | ASTEmptyPipeNode | ASTCodeNode | ASTComment | ASTDoctype | ASTIncludeNode | ASTMixinNode | ASTMixinSlotNode | ASTFilterNode | ASTEachNode | ASTConditionalNode | ASTCaseNode | ASTCaseWhenNode;
|
|
4
|
+
export type ASTTagNode = Omit<PugASTTagNode<ASTAttr, ASTBlock>, 'selfClosing' | 'isInline'> & AdditionalASTData;
|
|
5
5
|
export type ASTTextNode = Omit<PugASTTextNode, 'val'> & AdditionalASTData;
|
|
6
|
+
export type ASTEmptyPipeNode = PugASTEmptyPipeNode & AdditionalASTData;
|
|
6
7
|
export type ASTCodeNode = PugASTCodeNode & AdditionalASTData;
|
|
7
8
|
export type ASTComment = PugASTCommentNode & AdditionalASTData;
|
|
8
9
|
export type ASTDoctype = PugASTDoctypeNode & AdditionalASTData;
|
|
@@ -34,7 +35,12 @@ type PugASTTagNode<A, B> = {
|
|
|
34
35
|
name: string;
|
|
35
36
|
selfClosing: boolean;
|
|
36
37
|
attrs: A[];
|
|
37
|
-
attributeBlocks:
|
|
38
|
+
attributeBlocks: {
|
|
39
|
+
type: 'AttributeBlock';
|
|
40
|
+
val: string;
|
|
41
|
+
line: number;
|
|
42
|
+
column: number;
|
|
43
|
+
}[];
|
|
38
44
|
isInline: boolean;
|
|
39
45
|
line: number;
|
|
40
46
|
column: number;
|
|
@@ -47,6 +53,12 @@ type PugASTTextNode = {
|
|
|
47
53
|
line: number;
|
|
48
54
|
column: number;
|
|
49
55
|
};
|
|
56
|
+
type PugASTEmptyPipeNode = {
|
|
57
|
+
type: 'EmptyPipe';
|
|
58
|
+
val: string;
|
|
59
|
+
line: number;
|
|
60
|
+
column: number;
|
|
61
|
+
};
|
|
50
62
|
type PugASTCodeNode = {
|
|
51
63
|
type: 'Code';
|
|
52
64
|
val: string;
|
package/lib/pug-parser/index.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
1
|
import lexer from 'pug-lexer';
|
|
3
2
|
// @ts-ignore
|
|
4
3
|
import parser from 'pug-parser';
|
|
5
4
|
import { getOffsetFromLineAndCol } from '../utils/get-offset-from-line-and-col.js';
|
|
6
|
-
export function pugParse(pug) {
|
|
7
|
-
|
|
5
|
+
export function pugParse(pug, useOffset = false) {
|
|
6
|
+
let lexOrigin = lexer(pug);
|
|
7
|
+
/**
|
|
8
|
+
* Exclude indent and outdent tokens when offset is received to avoid indentation errors
|
|
9
|
+
*/
|
|
10
|
+
if (useOffset) {
|
|
11
|
+
const newLexOrigin = [];
|
|
12
|
+
for (const token of lexOrigin) {
|
|
13
|
+
if (token.type === 'indent' || token.type === 'outdent') {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
newLexOrigin.push(token);
|
|
17
|
+
}
|
|
18
|
+
lexOrigin = newLexOrigin;
|
|
19
|
+
}
|
|
8
20
|
const lex = JSON.parse(JSON.stringify(lexOrigin));
|
|
9
21
|
const originAst = parser(lexOrigin);
|
|
10
22
|
const ast = optimizeAST(originAst, lex, pug);
|
|
@@ -74,6 +86,7 @@ tokens, pug) {
|
|
|
74
86
|
endColumn,
|
|
75
87
|
block,
|
|
76
88
|
attrs,
|
|
89
|
+
attributeBlocks: node.attributeBlocks,
|
|
77
90
|
};
|
|
78
91
|
nodes.push(tagNode);
|
|
79
92
|
continue;
|
|
@@ -222,6 +235,27 @@ tokens, pug) {
|
|
|
222
235
|
const { endOffset, endLine, endColumn } = getRawTextAndLocationEnd(node.val, offset, line, column, tokens, offsets, pug);
|
|
223
236
|
const raw = pug.slice(offset, endOffset);
|
|
224
237
|
// console.log({ v: node.val, r: raw });
|
|
238
|
+
/**
|
|
239
|
+
*
|
|
240
|
+
* Empty piped line
|
|
241
|
+
*
|
|
242
|
+
* @see https://pugjs.org/language/plain-text.html#recommended-solutions
|
|
243
|
+
*/
|
|
244
|
+
if (raw === '|') {
|
|
245
|
+
const newNode = {
|
|
246
|
+
type: 'EmptyPipe',
|
|
247
|
+
raw,
|
|
248
|
+
val: node.val,
|
|
249
|
+
offset,
|
|
250
|
+
endOffset,
|
|
251
|
+
line,
|
|
252
|
+
endLine,
|
|
253
|
+
column,
|
|
254
|
+
endColumn,
|
|
255
|
+
};
|
|
256
|
+
nodes.push(newNode);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
225
259
|
const textNode = {
|
|
226
260
|
type: node.type,
|
|
227
261
|
raw,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/pug-parser",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
4
4
|
"description": "Pug parser for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"clean": "tsc --build --clean"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@markuplint/html-parser": "4.0.0-
|
|
25
|
-
"@markuplint/ml-ast": "4.0.0-
|
|
26
|
-
"@markuplint/parser-utils": "4.0.0-
|
|
24
|
+
"@markuplint/html-parser": "4.0.0-rc.0",
|
|
25
|
+
"@markuplint/ml-ast": "4.0.0-rc.0",
|
|
26
|
+
"@markuplint/parser-utils": "4.0.0-rc.0",
|
|
27
27
|
"pug-lexer": "^5.0.1",
|
|
28
28
|
"pug-parser": "^6.0.0"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "3fdeb45cb69ed52b3a215a7520cea1181601443f"
|
|
31
31
|
}
|