@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 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): (import("@markuplint/ml-ast").MLASTHTMLAttr & {
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 { getNamespace, parser as htmlParser } from '@markuplint/html-parser';
2
- import { ParserError, Parser, AttrState, removeQuote, scriptParser } from '@markuplint/parser-utils';
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 = htmlParser.parse(originNode.raw, {
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
- return htmlDoc.nodeList;
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: originNode.attrs.map(attr => {
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: token.raw[0] === '#' ? 'id' : 'class',
127
- potentialValue: attr.value.raw.slice(1),
128
- isDuplicatable: attr.potentialName === 'class',
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
- if (token.type === 'Numeric' || token.type === 'Boolean') {
147
- this.updateAttr(attr, { potentialValue: token.value });
148
- }
149
- else if (token.type === 'String' || token.type === 'Template') {
150
- this.updateAttr(attr, { potentialValue: removeQuote(token.value) });
151
- }
152
- else {
153
- this.updateAttr(attr, { isDynamicValue: true });
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
- else {
157
- this.updateAttr(attr, { isDynamicValue: true });
158
- }
159
- return attr;
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>, 'attributeBlocks' | 'selfClosing' | 'isInline'> & AdditionalASTData;
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: never[];
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;
@@ -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
- const lexOrigin = lexer(pug);
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-dev.23+d6f2aa9bc",
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-dev.23+d6f2aa9bc",
25
- "@markuplint/ml-ast": "4.0.0-dev.23+d6f2aa9bc",
26
- "@markuplint/parser-utils": "4.0.0-dev.23+d6f2aa9bc",
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": "d6f2aa9bc287768466f23b5340e4e0eecfa30d59"
30
+ "gitHead": "3fdeb45cb69ed52b3a215a7520cea1181601443f"
31
31
  }