Skip to content

Commit 40c7f27

Browse files
authored
Support for omit end tag (#15)
1 parent 2a5b2b5 commit 40c7f27

File tree

6 files changed

+9333
-410
lines changed

6 files changed

+9333
-410
lines changed

src/astro/index.ts

Lines changed: 122 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,18 @@ export function isParent(node: Node): node is ParentNode {
3333
export function walkElements(
3434
parent: ParentNode,
3535
code: string,
36-
cb: (n: Node, parents: ParentNode[]) => void,
36+
enter: (n: Node, parents: ParentNode[]) => void,
37+
leave: (n: Node, parents: ParentNode[]) => void,
3738
parents: ParentNode[] = [],
3839
): void {
3940
const children = getSortedChildren(parent, code)
4041
const currParents = [parent, ...parents]
4142
for (const node of children) {
42-
cb(node, currParents)
43+
enter(node, currParents)
4344
if (isParent(node)) {
44-
walkElements(node, code, cb, currParents)
45+
walkElements(node, code, enter, leave, currParents)
4546
}
47+
leave(node, currParents)
4648
}
4749
}
4850

@@ -51,25 +53,23 @@ export function walk(
5153
parent: ParentNode,
5254
code: string,
5355
enter: (n: Node | AttributeNode, parents: ParentNode[]) => void,
54-
leave?: (n: Node | AttributeNode, parents: ParentNode[]) => void,
55-
parents: ParentNode[] = [],
56+
leave: (n: Node | AttributeNode, parents: ParentNode[]) => void,
5657
): void {
57-
const children = getSortedChildren(parent, code)
58-
const currParents = [parent, ...parents]
59-
for (const node of children) {
60-
enter(node, currParents)
61-
if (isTag(node)) {
62-
const attrParents = [node, ...currParents]
63-
for (const attr of node.attributes) {
64-
enter(attr, attrParents)
65-
leave?.(attr, attrParents)
58+
walkElements(
59+
parent,
60+
code,
61+
(node, parents) => {
62+
enter(node, parents)
63+
if (isTag(node)) {
64+
const attrParents = [node, ...parents]
65+
for (const attr of node.attributes) {
66+
enter(attr, attrParents)
67+
leave(attr, attrParents)
68+
}
6669
}
67-
}
68-
if (isParent(node)) {
69-
walk(node, code, enter, leave, currParents)
70-
}
71-
leave?.(node, currParents)
72-
}
70+
},
71+
leave,
72+
)
7373
}
7474

7575
/**
@@ -103,44 +103,21 @@ export function getTagEndOffset(
103103
if (node.position!.end?.offset != null) {
104104
return node.position!.end.offset
105105
}
106+
let beforeIndex: number
106107
if (node.children.length) {
107-
const code = ctx.code
108-
let nextElementIndex = code.length
109-
const parent = parents[0]
110-
const childIndex = parent.children.indexOf(node)
111-
if (childIndex === parent.children.length - 1) {
112-
// last
113-
if (isTag(parent)) {
114-
nextElementIndex = getTagEndOffset(
115-
parent,
116-
parents.slice(1),
117-
ctx,
118-
)
119-
nextElementIndex = code.lastIndexOf("</", nextElementIndex)
120-
} else if (parent.type === "expression") {
121-
nextElementIndex = getExpressionEndOffset(
122-
parent,
123-
parents.slice(1),
124-
ctx,
125-
)
126-
nextElementIndex = code.lastIndexOf("}", nextElementIndex)
127-
}
128-
} else {
129-
const next = parent.children[childIndex + 1]
130-
nextElementIndex = next.position!.start.offset
131-
}
132-
return code.lastIndexOf(">", nextElementIndex)
108+
const lastChild = node.children[node.children.length - 1]
109+
beforeIndex = getEndOffset(lastChild, [node, ...parents], ctx)
110+
} else {
111+
beforeIndex = getStartTagEndOffset(node, ctx)
133112
}
134-
135-
let beforeIndex = getStartTagEndOffset(node, ctx)
136113
beforeIndex = skipSpaces(ctx.code, beforeIndex)
137114

138115
if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
139116
beforeIndex = beforeIndex + 2 + node.name.length
140117
const info = getTokenInfo(ctx, [">"], beforeIndex)
141118
return info.index + info.match.length
142119
}
143-
return ctx.code.length
120+
return beforeIndex
144121
}
145122

146123
/**
@@ -155,32 +132,10 @@ export function getExpressionEndOffset(
155132
return node.position!.end.offset
156133
}
157134
if (node.children.length) {
158-
const code = ctx.code
159-
let nextElementIndex = code.length
160-
const parent = parents[0]
161-
const childIndex = parent.children.indexOf(node)
162-
if (childIndex === parent.children.length - 1) {
163-
// last
164-
if (isTag(parent)) {
165-
nextElementIndex = getTagEndOffset(
166-
parent,
167-
parents.slice(1),
168-
ctx,
169-
)
170-
nextElementIndex = code.lastIndexOf("</", nextElementIndex)
171-
} else if (parent.type === "expression") {
172-
nextElementIndex = getExpressionEndOffset(
173-
parent,
174-
parents.slice(1),
175-
ctx,
176-
)
177-
nextElementIndex = code.lastIndexOf("}", nextElementIndex)
178-
}
179-
} else {
180-
const next = parent.children[childIndex + 1]
181-
nextElementIndex = next.position!.start.offset
182-
}
183-
return code.lastIndexOf("}", nextElementIndex)
135+
const lastChild = node.children[node.children.length - 1]
136+
const beforeIndex = getEndOffset(lastChild, [node, ...parents], ctx)
137+
const info = getTokenInfo(ctx, ["}"], beforeIndex)
138+
return info.index + info.match.length
184139
}
185140
const info = getTokenInfo(ctx, ["{", "}"], node.position!.start.offset)
186141
return info.index + info.match.length
@@ -285,7 +240,35 @@ export function getCommentEndOffset(node: CommentNode, ctx: Context): number {
285240
}
286241

287242
/**
288-
* If the given tag is a void tag, get the self-closing tag.
243+
* Get content end offset
244+
*/
245+
export function getContentEndOffset(
246+
parent: ParentNode,
247+
parents: ParentNode[],
248+
ctx: Context,
249+
): number {
250+
const code = ctx.code
251+
if (isTag(parent)) {
252+
const end = getTagEndOffset(parent, parents, ctx)
253+
if (code[end - 1] !== ">") {
254+
return end
255+
}
256+
const index = code.lastIndexOf("</", end)
257+
if (index >= 0 && code.slice(index, end).trim() === parent.name) {
258+
return index
259+
}
260+
return end
261+
} else if (parent.type === "expression") {
262+
const end = getExpressionEndOffset(parent, parents, ctx)
263+
return code.lastIndexOf("}", end)
264+
} else if (parent.type === "root") {
265+
return code.length
266+
}
267+
throw new Error(`unknown type: ${(parent as any).type}`)
268+
}
269+
270+
/**
271+
* If the given tag is a self-close tag, get the self-closing tag.
289272
*/
290273
export function getSelfClosingTag(
291274
node: TagLikeNode,
@@ -307,17 +290,7 @@ export function getSelfClosingTag(
307290
const childIndex = parent.children.indexOf(node)
308291
if (childIndex === parent.children.length - 1) {
309292
// last
310-
if (isTag(parent)) {
311-
nextElementIndex = getTagEndOffset(parent, parents.slice(1), ctx)
312-
nextElementIndex = code.lastIndexOf("</", nextElementIndex)
313-
} else if (parent.type === "expression") {
314-
nextElementIndex = getExpressionEndOffset(
315-
parent,
316-
parents.slice(1),
317-
ctx,
318-
)
319-
nextElementIndex = code.lastIndexOf("}", nextElementIndex)
320-
}
293+
nextElementIndex = getContentEndOffset(parent, parents.slice(1), ctx)
321294
} else {
322295
const next = parent.children[childIndex + 1]
323296
nextElementIndex = next.position!.start.offset
@@ -332,6 +305,68 @@ export function getSelfClosingTag(
332305
end: code.slice(endOffset - 2, endOffset) === "/>" ? "/>" : ">",
333306
}
334307
}
308+
/**
309+
* If the given tag has a end tag, get the end tag.
310+
*/
311+
export function getEndTag(
312+
node: TagLikeNode,
313+
parents: ParentNode[],
314+
ctx: Context,
315+
): null | {
316+
offset: number
317+
tag: string
318+
} {
319+
let beforeIndex: number
320+
if (node.children.length) {
321+
const lastChild = node.children[node.children.length - 1]
322+
beforeIndex = getEndOffset(lastChild, [node, ...parents], ctx)
323+
} else {
324+
beforeIndex = getStartTagEndOffset(node, ctx)
325+
}
326+
beforeIndex = skipSpaces(ctx.code, beforeIndex)
327+
328+
if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
329+
const offset = beforeIndex
330+
beforeIndex = beforeIndex + 2 + node.name.length
331+
const info = getTokenInfo(ctx, [">"], beforeIndex)
332+
const end = info.index + info.match.length
333+
return {
334+
offset,
335+
tag: ctx.code.slice(offset, end),
336+
}
337+
}
338+
return null
339+
}
340+
341+
/**
342+
* Get end offset of tag
343+
*/
344+
function getEndOffset(node: Node, parents: ParentNode[], ctx: Context): number {
345+
if (node.position!.end?.offset != null) {
346+
return node.position!.end.offset
347+
}
348+
if (isTag(node)) return getTagEndOffset(node, parents, ctx)
349+
if (node.type === "expression")
350+
return getExpressionEndOffset(node, parents, ctx)
351+
if (node.type === "comment") return getCommentEndOffset(node, ctx)
352+
if (node.type === "frontmatter") {
353+
const start = node.position!.start.offset
354+
return ctx.code.indexOf("---", start + 3) + 3
355+
}
356+
if (node.type === "doctype") {
357+
const start = node.position!.start.offset
358+
return ctx.code.indexOf(">", start) + 1
359+
}
360+
if (node.type === "text") {
361+
const start = node.position!.start.offset
362+
return start + node.value.length
363+
}
364+
if (node.type === "root") {
365+
return ctx.code.length
366+
}
367+
368+
throw new Error(`unknown type: ${(node as any).type}`)
369+
}
335370

336371
/**
337372
* Get token info

src/context/script.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class ScriptContext {
2121
private readonly restoreNodeProcesses: ((
2222
node: TSESTree.Node,
2323
result: ESLintExtendedProgram,
24+
parent: TSESTree.Node,
2425
) => boolean)[] = []
2526

2627
public constructor(ctx: Context) {
@@ -54,6 +55,7 @@ export class ScriptContext {
5455
process: (
5556
node: TSESTree.Node,
5657
result: ESLintExtendedProgram,
58+
parent: TSESTree.Node,
5759
) => boolean,
5860
): void {
5961
this.restoreNodeProcesses.push(process)
@@ -89,12 +91,12 @@ export class ScriptContext {
8991

9092
// remap locations
9193

92-
const traversed = new Set<TSESTree.Node>()
94+
const traversed = new Map<TSESTree.Node, TSESTree.Node | null>()
9395
traverseNodes(result.ast, {
9496
visitorKeys: result.visitorKeys,
95-
enterNode: (node) => {
97+
enterNode: (node, p) => {
9698
if (!traversed.has(node)) {
97-
traversed.add(node)
99+
traversed.set(node, p)
98100

99101
this.remapLocation(node)
100102
}
@@ -121,9 +123,10 @@ export class ScriptContext {
121123
}
122124

123125
let restoreNodeProcesses = this.restoreNodeProcesses
124-
for (const node of traversed) {
126+
for (const [node, parent] of traversed) {
127+
if (!parent) continue
125128
restoreNodeProcesses = restoreNodeProcesses.filter(
126-
(proc) => !proc(node, result),
129+
(proc) => !proc(node, result, parent),
127130
)
128131
}
129132

0 commit comments

Comments
 (0)