-
-
Notifications
You must be signed in to change notification settings - Fork 357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(transformers): Ignore empty lines on notation transformer range #755
base: main
Are you sure you want to change the base?
Changes from all commits
4086ef1
839994d
53bba31
dcda86a
61ec879
3f61e85
993e24f
3294e7a
214e9e0
c7a40d8
594ed69
5fb6012
d994692
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import type { Element, Text } from 'hast' | ||
import type { ShikiTransformer, ShikiTransformerContext } from 'shiki' | ||
|
||
export function createCommentNotationTransformer( | ||
name: string, | ||
regex: RegExp, | ||
onMatch: ( | ||
this: ShikiTransformerContext, | ||
match: string[], | ||
line: Element, | ||
commentNode: Element, | ||
lines: Element[], | ||
index: number, | ||
) => boolean, | ||
removeEmptyLines = false, | ||
): ShikiTransformer { | ||
return { | ||
name, | ||
code(code) { | ||
const lines = code.children.filter(i => i.type === 'element') as Element[] | ||
const linesToRemove: (Element | Text)[] = [] | ||
lines.forEach((line, idx) => { | ||
let nodeToRemove: Element | undefined | ||
|
||
for (const child of line.children) { | ||
if (child.type !== 'element') | ||
continue | ||
const text = child.children[0] | ||
if (text.type !== 'text') | ||
continue | ||
|
||
let replaced = false | ||
text.value = text.value.replace(regex, (...match) => { | ||
if (onMatch.call(this, match, line, child, lines, idx)) { | ||
replaced = true | ||
return '' | ||
} | ||
return match[0] | ||
}) | ||
if (replaced && !text.value.trim()) | ||
nodeToRemove = child | ||
} | ||
|
||
if (nodeToRemove) { | ||
line.children.splice(line.children.indexOf(nodeToRemove), 1) | ||
|
||
// Remove if empty | ||
if (line.children.length === 0) { | ||
linesToRemove.push(line) | ||
if (removeEmptyLines) { | ||
const next = code.children[code.children.indexOf(line) + 1] | ||
if (next && next.type === 'text' && next.value === '\n') | ||
linesToRemove.push(next) | ||
} | ||
} | ||
} | ||
}) | ||
|
||
for (const line of linesToRemove) | ||
code.children.splice(code.children.indexOf(line), 1) | ||
}, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,67 @@ | ||
import type { Element, Text } from 'hast' | ||
import type { ShikiTransformer, ShikiTransformerContext } from 'shiki' | ||
|
||
export function createCommentNotationTransformer( | ||
/** | ||
* Regex that matches code comments | ||
*/ | ||
const regex = /((?:\/\/|\/\*|<!--|[#"']|--|%%|;{1,2}|%{1,2})\s*)(\S.*?)(-->|\*\/|$)/ | ||
|
||
/** | ||
* Create a transformer to process comment notations | ||
* | ||
* @param name transformer name | ||
* @param onMatch function to be called when found a comment in code, return the replaced text. | ||
*/ | ||
export function createCommentNotationTransformerExperimental( | ||
name: string, | ||
regex: RegExp, | ||
onMatch: ( | ||
this: ShikiTransformerContext, | ||
match: string[], | ||
commentText: string, | ||
fuma-nama marked this conversation as resolved.
Show resolved
Hide resolved
|
||
line: Element, | ||
commentNode: Element, | ||
lines: Element[], | ||
index: number, | ||
) => boolean, | ||
removeEmptyLines = false, | ||
) => string, | ||
): ShikiTransformer { | ||
return { | ||
name, | ||
code(code) { | ||
const lines = code.children.filter(i => i.type === 'element') as Element[] | ||
const linesToRemove: (Element | Text)[] = [] | ||
lines.forEach((line, idx) => { | ||
let nodeToRemove: Element | undefined | ||
|
||
for (const child of line.children) { | ||
if (child.type !== 'element') | ||
continue | ||
const text = child.children[0] | ||
if (text.type !== 'text') | ||
continue | ||
|
||
let replaced = false | ||
text.value = text.value.replace(regex, (...match) => { | ||
if (onMatch.call(this, match, line, child, lines, idx)) { | ||
replaced = true | ||
return '' | ||
} | ||
return match[0] | ||
}) | ||
if (replaced && !text.value.trim()) | ||
nodeToRemove = child | ||
} | ||
|
||
if (nodeToRemove) { | ||
line.children.splice(line.children.indexOf(nodeToRemove), 1) | ||
|
||
// Remove if empty | ||
if (line.children.length === 0) { | ||
linesToRemove.push(line) | ||
if (removeEmptyLines) { | ||
const next = code.children[code.children.indexOf(line) + 1] | ||
if (next && next.type === 'text' && next.value === '\n') | ||
linesToRemove.push(next) | ||
} | ||
} | ||
lines.forEach((line, lineIdx) => { | ||
// comment should be at the end of line (last token) | ||
const last = line.children.findLast(i => i.type === 'element') as Element | undefined | ||
|
||
if (!last || last.children.length === 0) | ||
return | ||
const text = last.children[0] | ||
if (text.type !== 'text') | ||
return | ||
|
||
let deleteComment = false | ||
|
||
const isEmptyLine = line.children.length === 1 | ||
text.value = text.value.replace(regex, (_, prefix, text, end) => { | ||
// no other tokens except the comment | ||
const replaced = onMatch.call(this, text, line, last, lines, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the second arg Instead of making a new function as breaking changes, couldn't we just allow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the new so we pass the As you see, the For the problem about breaking change, I think we could also create a wrapper over the original function, would like to hear your opinions about this. |
||
// take the next line if the current line will be removed | ||
isEmptyLine ? lineIdx + 1 : lineIdx) | ||
|
||
if (replaced.trim().length === 0) | ||
deleteComment = true | ||
|
||
return prefix + replaced + end | ||
}) | ||
|
||
if (!deleteComment) | ||
return | ||
|
||
if (isEmptyLine) { | ||
linesToRemove.push(line) | ||
} | ||
else { | ||
line.children.splice(line.children.indexOf(last), 1) | ||
} | ||
}) | ||
|
||
|
@@ -61,3 +70,5 @@ export function createCommentNotationTransformer( | |
}, | ||
} | ||
} | ||
|
||
export { createCommentNotationTransformer } from './utils-legacy' |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if we should hard-code the regex, this makes the code less flexiable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm do you mean to allow passing the entire regex, including the part for matching comments? (like the previous code)
I assume
createCommentNotationTransformer
is for matching comment notations, it sounds reasonable to me to add the regex for matching comment inside the utility. I can revert it if you wanted