@markuplint/parser-utils
Advanced tools
Comparing version 4.0.0-dev.10 to 4.0.0-dev.12
import type { QuoteSet } from './types.js'; | ||
import type { MLASTHTMLAttr } from '@markuplint/ml-ast'; | ||
import { AttrState } from './attr-parser.js'; | ||
export declare function attrTokenizer(raw: string, line: number, col: number, startOffset: number, quoteSet?: ReadonlyArray<QuoteSet>, startState?: AttrState, quoteInValueChars?: ReadonlyArray<QuoteSet>, spaces?: ReadonlyArray<string>): MLASTHTMLAttr & { | ||
__leftover?: string; | ||
import { AttrState } from './enums.js'; | ||
/** | ||
* @see https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state | ||
* @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state | ||
* @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state | ||
*/ | ||
export declare function attrTokenizer(raw: string, quoteSet?: readonly QuoteSet[], startState?: AttrState, quoteInValueChars?: ReadonlyArray<QuoteSet>, endOfUnquotedValueChars?: ReadonlyArray<string>): { | ||
spacesBeforeAttrName: string; | ||
attrName: string; | ||
spacesBeforeEqual: string; | ||
equal: string; | ||
spacesAfterEqual: string; | ||
quoteStart: string; | ||
attrValue: string; | ||
quoteEnd: string; | ||
leftover: string; | ||
}; |
@@ -1,75 +0,169 @@ | ||
import { AttrState, attrParser } from './attr-parser.js'; | ||
import { tokenizer, uuid } from './create-token.js'; | ||
export function attrTokenizer(raw, line, col, startOffset, quoteSet, startState = AttrState.BeforeName, quoteInValueChars, spaces) { | ||
const parsed = attrParser(raw, quoteSet, startState, quoteInValueChars, spaces); | ||
let offset = startOffset; | ||
const spacesBeforeName = tokenizer(parsed.spacesBeforeAttrName, line, col, offset); | ||
line = spacesBeforeName.endLine; | ||
col = spacesBeforeName.endCol; | ||
offset = spacesBeforeName.endOffset; | ||
const name = tokenizer(parsed.attrName, line, col, offset); | ||
line = name.endLine; | ||
col = name.endCol; | ||
offset = name.endOffset; | ||
const spacesBeforeEqual = tokenizer(parsed.spacesBeforeEqual, line, col, offset); | ||
line = spacesBeforeEqual.endLine; | ||
col = spacesBeforeEqual.endCol; | ||
offset = spacesBeforeEqual.endOffset; | ||
const equal = tokenizer(parsed.equal, line, col, offset); | ||
line = equal.endLine; | ||
col = equal.endCol; | ||
offset = equal.endOffset; | ||
const spacesAfterEqual = tokenizer(parsed.spacesAfterEqual, line, col, offset); | ||
line = spacesAfterEqual.endLine; | ||
col = spacesAfterEqual.endCol; | ||
offset = spacesAfterEqual.endOffset; | ||
const startQuote = tokenizer(parsed.quoteStart, line, col, offset); | ||
line = startQuote.endLine; | ||
col = startQuote.endCol; | ||
offset = startQuote.endOffset; | ||
const value = tokenizer(parsed.attrValue, line, col, offset); | ||
line = value.endLine; | ||
col = value.endCol; | ||
offset = value.endOffset; | ||
const endQuote = tokenizer(parsed.quoteEnd, line, col, offset); | ||
const attrToken = tokenizer(parsed.attrName + | ||
parsed.spacesBeforeEqual + | ||
parsed.equal + | ||
parsed.spacesAfterEqual + | ||
parsed.quoteStart + | ||
parsed.attrValue + | ||
parsed.quoteEnd, name.startLine, name.startCol, name.startOffset); | ||
const result = { | ||
type: 'html-attr', | ||
uuid: uuid(), | ||
raw: attrToken.raw, | ||
startOffset: attrToken.startOffset, | ||
endOffset: attrToken.endOffset, | ||
startLine: attrToken.startLine, | ||
endLine: attrToken.endLine, | ||
startCol: attrToken.startCol, | ||
endCol: attrToken.endCol, | ||
spacesBeforeName, | ||
name, | ||
import { defaultSpaces } from './const.js'; | ||
import { AttrState } from './enums.js'; | ||
const defaultQuoteSet = [ | ||
{ start: '"', end: '"' }, | ||
{ start: "'", end: "'" }, | ||
]; | ||
const defaultQuoteInValueChars = []; | ||
const spaces = defaultSpaces; | ||
const EQUAL = '='; | ||
/** | ||
* @see https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state | ||
* @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state | ||
* @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state | ||
*/ | ||
export function attrTokenizer(raw, quoteSet = defaultQuoteSet, startState = AttrState.BeforeName, quoteInValueChars = defaultQuoteInValueChars, endOfUnquotedValueChars = [...defaultSpaces, '/', '>']) { | ||
let state = startState; | ||
let spacesBeforeAttrName = ''; | ||
let attrName = ''; | ||
let spacesBeforeEqual = ''; | ||
let equal = ''; | ||
let spacesAfterEqual = ''; | ||
let quoteTypeIndex = -1; | ||
let quoteStart = ''; | ||
let attrValue = ''; | ||
let quoteEnd = ''; | ||
const isBeforeValueStarted = startState === AttrState.BeforeValue; | ||
const quoteModeStack = []; | ||
const chars = [...raw]; | ||
while (chars.length > 0) { | ||
if (state === AttrState.AfterValue) { | ||
break; | ||
} | ||
const char = chars.shift(); | ||
switch (state) { | ||
case AttrState.BeforeName: { | ||
if (char === '>') { | ||
chars.unshift(char); | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
if (char === '/') { | ||
chars.unshift(char); | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
if (spaces.includes(char)) { | ||
spacesBeforeAttrName += char; | ||
break; | ||
} | ||
attrName += char; | ||
state = AttrState.Name; | ||
break; | ||
} | ||
case AttrState.Name: { | ||
if (char === '>') { | ||
chars.unshift(char); | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
if (char === '/') { | ||
chars.unshift(char); | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
if (spaces.includes(char)) { | ||
spacesBeforeEqual += char; | ||
state = AttrState.Equal; | ||
break; | ||
} | ||
if (char === EQUAL) { | ||
equal += char; | ||
state = AttrState.BeforeValue; | ||
break; | ||
} | ||
attrName += char; | ||
break; | ||
} | ||
case AttrState.Equal: { | ||
if (spaces.includes(char)) { | ||
spacesBeforeEqual += char; | ||
break; | ||
} | ||
if (char === EQUAL) { | ||
equal += char; | ||
state = AttrState.BeforeValue; | ||
break; | ||
} | ||
// End of attribute | ||
chars.unshift(spacesBeforeEqual, char); | ||
spacesBeforeEqual = ''; | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
case AttrState.BeforeValue: { | ||
if (endOfUnquotedValueChars.includes(char) && spaces.includes(char)) { | ||
if (isBeforeValueStarted) { | ||
spacesBeforeAttrName += char; | ||
break; | ||
} | ||
spacesAfterEqual += char; | ||
break; | ||
} | ||
quoteTypeIndex = quoteSet.findIndex(quote => quote.start === char); | ||
const quote = quoteSet[quoteTypeIndex]; | ||
if (quote) { | ||
quoteStart = quote.start; | ||
state = AttrState.Value; | ||
break; | ||
} | ||
const raw = char + chars.join(''); | ||
const inQuote = quoteInValueChars.find(quote => raw.startsWith(quote.start)); | ||
if (inQuote) { | ||
quoteModeStack.push(inQuote); | ||
attrValue += inQuote.start; | ||
chars.splice(0, inQuote.start.length - 1); | ||
state = AttrState.Value; | ||
break; | ||
} | ||
chars.unshift(char); | ||
state = AttrState.Value; | ||
break; | ||
} | ||
case AttrState.Value: { | ||
if (!quoteSet[quoteTypeIndex] && endOfUnquotedValueChars.includes(char)) { | ||
chars.unshift(char); | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
if (quoteModeStack.length === 0 && char === quoteSet[quoteTypeIndex]?.end) { | ||
quoteEnd = char; | ||
state = AttrState.AfterValue; | ||
break; | ||
} | ||
const raw = char + chars.join(''); | ||
const inQuoteEnd = quoteModeStack.at(-1); | ||
if (inQuoteEnd && raw.startsWith(inQuoteEnd.end)) { | ||
quoteModeStack.pop(); | ||
attrValue += inQuoteEnd.end; | ||
chars.splice(0, inQuoteEnd.end.length - 1); | ||
break; | ||
} | ||
const inQuoteStart = quoteInValueChars.find(quote => raw.startsWith(quote.start)); | ||
if (inQuoteStart) { | ||
quoteModeStack.push(inQuoteStart); | ||
attrValue += inQuoteStart.start; | ||
chars.splice(0, inQuoteStart.start.length - 1); | ||
break; | ||
} | ||
attrValue += char; | ||
break; | ||
} | ||
} | ||
} | ||
if (state === AttrState.Value && quoteTypeIndex !== -1) { | ||
throw new SyntaxError(`Unclosed attribute value: ${raw}`); | ||
} | ||
const leftover = chars.join(''); | ||
return { | ||
spacesBeforeAttrName, | ||
attrName, | ||
spacesBeforeEqual, | ||
equal, | ||
spacesAfterEqual, | ||
startQuote, | ||
value, | ||
endQuote, | ||
isDuplicatable: false, | ||
nodeName: name.raw, | ||
parentNode: null, | ||
prevNode: null, | ||
nextNode: null, | ||
isFragment: false, | ||
isGhost: false, | ||
quoteStart, | ||
attrValue, | ||
quoteEnd, | ||
leftover, | ||
}; | ||
if (parsed.leftover) { | ||
return { | ||
...result, | ||
__leftover: parsed.leftover, | ||
}; | ||
} | ||
return result; | ||
} |
@@ -14,4 +14,5 @@ export declare const MASK_CHAR = "\uE000"; | ||
* - U+000C FORM FEED (FF) => `\f` | ||
* - U+000D CARRIAGE RETURN (CR) => `\r` | ||
* - U+0020 SPACE => ` ` | ||
*/ | ||
export declare const defaultSpaces: readonly ["\t", "\n", "\f", " "]; | ||
export declare const defaultSpaces: readonly ["\t", "\n", "\f", "\r", " "]; |
@@ -103,4 +103,5 @@ export const MASK_CHAR = '\uE000'; | ||
* - U+000C FORM FEED (FF) => `\f` | ||
* - U+000D CARRIAGE RETURN (CR) => `\r` | ||
* - U+0020 SPACE => ` ` | ||
*/ | ||
export const defaultSpaces = ['\t', '\n', '\f', ' ']; | ||
export const defaultSpaces = ['\t', '\n', '\f', '\r', ' ']; |
import type { MLASTAttr, MLASTNode } from '@markuplint/ml-ast'; | ||
export declare function nodeListToDebugMaps(nodeList: MLASTNode[], withAttr?: boolean): string[]; | ||
export declare function attributesToDebugMaps(attributes: MLASTAttr[]): string[][]; | ||
export declare function nodeListToDebugMaps(nodeList: readonly (MLASTNode | null)[], withAttr?: boolean): string[]; | ||
export declare function attributesToDebugMaps(attributes: readonly MLASTAttr[]): string[][]; | ||
export declare function nodeTreeDebugView(nodeTree: readonly MLASTNode[]): (string | undefined)[]; |
@@ -1,21 +0,12 @@ | ||
export function nodeListToDebugMaps( | ||
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types | ||
nodeList, withAttr = false) { | ||
export function nodeListToDebugMaps(nodeList, withAttr = false) { | ||
return nodeList.flatMap(n => { | ||
const r = []; | ||
if (n.isGhost) { | ||
r.push(`[N/A]>[N/A](N/A)${n.nodeName}: ${visibleWhiteSpace(n.raw)}`); | ||
r.push(tokenDebug(n)); | ||
if (withAttr && n && n.type === 'starttag') { | ||
r.push(...attributesToDebugMaps(n.attributes).flat()); | ||
} | ||
else { | ||
r.push(tokenDebug(n)); | ||
if (withAttr && 'attributes' in n) { | ||
r.push(...attributesToDebugMaps(n.attributes).flat()); | ||
} | ||
} | ||
return r; | ||
}); | ||
} | ||
export function attributesToDebugMaps( | ||
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types | ||
attributes) { | ||
export function attributesToDebugMaps(attributes) { | ||
return attributes.map(n => { | ||
@@ -25,12 +16,14 @@ const r = [ | ||
...n, | ||
name: n.type === 'html-attr' ? n.name.raw : n.raw, | ||
name: n.type === 'attr' ? n.name.raw : n.raw, | ||
}), | ||
]; | ||
if (n.type === 'html-attr') { | ||
r.push(` ${tokenDebug(n.spacesBeforeName, 'bN')}`, ` ${tokenDebug(n.name, 'name')}`, ` ${tokenDebug(n.spacesBeforeEqual, 'bE')}`, ` ${tokenDebug(n.equal, 'equal')}`, ` ${tokenDebug(n.spacesAfterEqual, 'aE')}`, ` ${tokenDebug(n.startQuote, 'sQ')}`, ` ${tokenDebug(n.value, 'value')}`, ` ${tokenDebug(n.endQuote, 'eQ')}`, ` isDirective: ${!!n.isDirective}`, ` isDynamicValue: ${!!n.isDynamicValue}`); | ||
if (n.type === 'spread') { | ||
r.push(` #spread: ${visibleWhiteSpace(n.raw)}`); | ||
return r; | ||
} | ||
r.push(` ${tokenDebug(n.spacesBeforeName, 'bN')}`, ` ${tokenDebug(n.name, 'name')}`, ` ${tokenDebug(n.spacesBeforeEqual, 'bE')}`, ` ${tokenDebug(n.equal, 'equal')}`, ` ${tokenDebug(n.spacesAfterEqual, 'aE')}`, ` ${tokenDebug(n.startQuote, 'sQ')}`, ` ${tokenDebug(n.value, 'value')}`, ` ${tokenDebug(n.endQuote, 'eQ')}`, ` isDirective: ${!!n.isDirective}`, ` isDynamicValue: ${!!n.isDynamicValue}`); | ||
if (n.potentialName != null) { | ||
r.push(` potentialName: ${visibleWhiteSpace(n.potentialName)}`); | ||
} | ||
if (n.type === 'html-attr' && n.candidate) { | ||
if (n.candidate) { | ||
r.push(` candidate: ${visibleWhiteSpace(n.candidate)}`); | ||
@@ -41,6 +34,29 @@ } | ||
} | ||
export function nodeTreeDebugView(nodeTree) { | ||
return nodeTree | ||
.map((n, i) => { | ||
const lines = []; | ||
if (n.type === 'attr' || n.type === 'spread') { | ||
return; | ||
} | ||
lines.push(`${i.toString().padStart(3, '0')}: [${n.uuid.slice(0, 8)}] ${' '.repeat(Math.max(n.depth, 0))}${n.type === 'endtag' ? '/' : ''}${n.nodeName}(${n.uuid.slice(0, 8)})${n.type === 'starttag' && n.isGhost ? '[👻]' : ''}${n.type === 'starttag' | ||
? ` => ${n.pairNode ? `/${n.pairNode.nodeName}(${n.pairNode.uuid.slice(0, 8)})` : '💀'}` | ||
: ''}`); | ||
if (n.type === 'starttag' || n.type === 'psblock') { | ||
for (const c of n.childNodes ?? []) { | ||
lines.push(`${' '.repeat(15)} ${' '.repeat(Math.max(n.depth, 0))}┗━ ${c.type === 'endtag' ? '/' : ''}${c.nodeName}(${c.uuid.slice(0, 8)})`); | ||
} | ||
} | ||
return lines; | ||
}) | ||
.filter(Boolean) | ||
.flat(); | ||
} | ||
function tokenDebug(n, type = '') { | ||
if (!n) { | ||
return 'NULL'; | ||
} | ||
return `[${n.startLine}:${n.startCol}]>[${n.endLine}:${n.endCol}](${n.startOffset},${n.endOffset})${ | ||
// @ts-ignore | ||
n.potentialName ?? n.nodeName ?? n.name ?? n.type ?? type}: ${visibleWhiteSpace(n.raw)}`; | ||
n.potentialName ?? n.nodeName ?? n.name ?? n.type ?? type}${'isGhost' in n && n.isGhost ? '(👻)' : ''}${'isBogus' in n && n.isBogus ? '(👿)' : ''}: ${visibleWhiteSpace(n.raw)}`; | ||
} | ||
@@ -47,0 +63,0 @@ function visibleWhiteSpace(chars) { |
@@ -1,13 +0,4 @@ | ||
export declare function getLine(html: string, startOffset: number): number; | ||
export declare function getCol(html: string, startOffset: number): number; | ||
export declare function getEndLine(html: string, line: number): number; | ||
export declare function getEndCol(html: string, col: number): number; | ||
export declare function sliceFragment(rawHtml: string, start: number, end: number): { | ||
startOffset: number; | ||
endOffset: number; | ||
startLine: number; | ||
endLine: number; | ||
startCol: number; | ||
endCol: number; | ||
raw: string; | ||
}; | ||
export declare function getLine(rawCodeFragment: string, startOffset: number): number; | ||
export declare function getCol(rawCodeFragment: string, startOffset: number): number; | ||
export declare function getEndLine(rawCodeFragment: string, startLine: number): number; | ||
export declare function getEndCol(rawCodeFragment: string, startCol: number): number; |
@@ -1,28 +0,17 @@ | ||
export function getLine(html, startOffset) { | ||
return html.slice(0, startOffset).split(/\n/).length; | ||
const LINE_BREAK = '\n'; | ||
export function getLine(rawCodeFragment, startOffset) { | ||
return rawCodeFragment.slice(0, startOffset).split(LINE_BREAK).length; | ||
} | ||
export function getCol(html, startOffset) { | ||
const lines = html.slice(0, startOffset).split(/\n/); | ||
export function getCol(rawCodeFragment, startOffset) { | ||
const lines = rawCodeFragment.slice(0, startOffset).split(LINE_BREAK); | ||
return (lines.at(-1) ?? '').length + 1; | ||
} | ||
export function getEndLine(html, line) { | ||
return html.split(/\r?\n/).length - 1 + line; | ||
export function getEndLine(rawCodeFragment, startLine) { | ||
return rawCodeFragment.split(LINE_BREAK).length - 1 + startLine; | ||
} | ||
export function getEndCol(html, col) { | ||
const lines = html.split(/\r?\n/); | ||
export function getEndCol(rawCodeFragment, startCol) { | ||
const lines = rawCodeFragment.split(LINE_BREAK); | ||
const lineCount = lines.length; | ||
const lastLine = lines.pop(); | ||
return lineCount > 1 ? lastLine.length + 1 : col + html.length; | ||
return lineCount > 1 ? lastLine.length + 1 : startCol + rawCodeFragment.length; | ||
} | ||
export function sliceFragment(rawHtml, start, end) { | ||
const raw = rawHtml.slice(start, end); | ||
return { | ||
startOffset: start, | ||
endOffset: end, | ||
startLine: getLine(rawHtml, start), | ||
endLine: getLine(rawHtml, end), | ||
startCol: getCol(rawHtml, start), | ||
endCol: getCol(rawHtml, end), | ||
raw, | ||
}; | ||
} |
@@ -0,4 +1,5 @@ | ||
import type { Parser } from './parser.js'; | ||
import type { IgnoreBlock, IgnoreTag } from './types.js'; | ||
import type { MLASTNode } from '@markuplint/ml-ast'; | ||
import type { MLASTNodeTreeItem } from '@markuplint/ml-ast'; | ||
export declare function ignoreBlock(source: string, tags: readonly IgnoreTag[], maskChar?: string): IgnoreBlock; | ||
export declare function restoreNode(nodeList: MLASTNode[], ignoreBlock: IgnoreBlock): MLASTNode[]; | ||
export declare function restoreNode(parser: Parser<any, any>, nodeList: readonly MLASTNodeTreeItem[], ignoreBlock: IgnoreBlock, throwErrorWhenTagHasUnresolved?: boolean): MLASTNodeTreeItem[]; |
import { MASK_CHAR } from './const.js'; | ||
import { uuid } from './create-token.js'; | ||
import { sliceFragment } from './get-location.js'; | ||
import { siblingsCorrection } from './siblings-correction.js'; | ||
import { getCol, getLine } from './get-location.js'; | ||
import { ParserError } from './parser-error.js'; | ||
export function ignoreBlock(source, tags, maskChar = MASK_CHAR) { | ||
@@ -9,12 +8,2 @@ let replaced = source; | ||
for (const tag of tags) { | ||
// Replace tags in attributes | ||
const attr = maskText(prepend(tag.start, '(?<=(?:"|\'))'), append(tag.end, '(?=(?:"|\'))'), replaced, (startTag, taggedCode, endTag) => { | ||
const mask = maskChar.repeat(startTag.length) + | ||
taggedCode.replaceAll(/[^\n]/g, maskChar) + | ||
maskChar.repeat((endTag ?? '').length); | ||
return mask; | ||
}); | ||
replaced = attr.replaced; | ||
stack.push(...attr.stack.map(res => ({ ...res, type: tag.type }))); | ||
// Replace tags in other nodes | ||
const text = maskText(tag.start, tag.end, replaced, (startTag, taggedCode, endTag) => { | ||
@@ -53,2 +42,3 @@ const mask = maskChar.repeat(startTag.length) + | ||
endTag: endTag ?? null, | ||
resolved: false, | ||
}); | ||
@@ -67,102 +57,37 @@ /** | ||
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types | ||
nodeList, ignoreBlock) { | ||
nodeList = [...nodeList]; | ||
parser, nodeList, | ||
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types | ||
ignoreBlock, throwErrorWhenTagHasUnresolved = true) { | ||
const newNodeList = [...nodeList]; | ||
const { source, stack, maskChar } = ignoreBlock; | ||
for (const node of nodeList) { | ||
if (node.type === 'comment' || node.type === 'text' || node.type === 'psblock') { | ||
if (!hasIgnoreBlock(node.raw, maskChar)) { | ||
continue; | ||
} | ||
const parentNode = node.parentNode; | ||
const index = nodeList.indexOf(node); | ||
const insertList = []; | ||
let text = node.raw; | ||
let pointer = 0; | ||
for (const tag of stack) { | ||
if (node.startOffset <= tag.index && tag.index < node.endOffset) { | ||
const start = tag.index - node.startOffset; | ||
const body = tag.startTag + tag.taggedCode + (tag.endTag ?? ''); | ||
const above = node.raw.slice(pointer, start); | ||
const below = text.slice(above.length + body.length); | ||
if (above) { | ||
const offset = node.startOffset + pointer; | ||
const { raw, startOffset, endOffset, startLine, endLine, startCol, endCol } = sliceFragment(source, offset, offset + above.length); | ||
const textNode = { | ||
...node, | ||
uuid: uuid(), | ||
type: 'text', | ||
raw, | ||
startOffset, | ||
endOffset, | ||
startLine, | ||
endLine, | ||
startCol, | ||
endCol, | ||
}; | ||
if (node.prevNode?.nextNode) { | ||
node.prevNode.nextNode = textNode; | ||
} | ||
if (node.nextNode?.prevNode) { | ||
node.nextNode.prevNode = textNode; | ||
} | ||
insertList.push(textNode); | ||
} | ||
if (body) { | ||
const offset = node.startOffset + pointer + above.length; | ||
const { raw, startOffset, endOffset, startLine, endLine, startCol, endCol } = sliceFragment(source, offset, offset + body.length); | ||
const bodyNode = { | ||
uuid: uuid(), | ||
type: 'psblock', | ||
nodeName: `#ps:${tag.type}`, | ||
raw, | ||
parentNode: node.parentNode, | ||
prevNode: null, | ||
nextNode: null, | ||
isFragment: node.isFragment, | ||
isGhost: false, | ||
startOffset, | ||
endOffset, | ||
startLine, | ||
endLine, | ||
startCol, | ||
endCol, | ||
}; | ||
if (node.prevNode?.nextNode) { | ||
node.prevNode.nextNode = bodyNode; | ||
} | ||
if (node.nextNode?.prevNode) { | ||
node.nextNode.prevNode = bodyNode; | ||
} | ||
insertList.push(bodyNode); | ||
} | ||
text = below; | ||
pointer = start + body.length; | ||
} | ||
} | ||
if (text) { | ||
const offset = node.endOffset - text.length; | ||
const { raw, startOffset, endOffset, startLine, endLine, startCol, endCol } = sliceFragment(source, offset, offset + text.length); | ||
const textNode = { | ||
...node, | ||
uuid: uuid(), | ||
type: 'text', | ||
raw, | ||
startOffset, | ||
endOffset, | ||
startLine, | ||
endLine, | ||
startCol, | ||
endCol, | ||
}; | ||
insertList.push(textNode); | ||
} | ||
siblingsCorrection(insertList); | ||
if (parentNode) { | ||
parentNode.childNodes = insertList; | ||
} | ||
nodeList.splice(index, 1, ...insertList); | ||
if (stack.length === 0) { | ||
return newNodeList; | ||
} | ||
for (const tag of stack) { | ||
const node = newNodeList.find(node => node.startOffset === tag.index); | ||
if (!node) { | ||
continue; | ||
} | ||
const raw = `${tag.startTag}${tag.taggedCode}${tag.endTag ?? ''}`; | ||
const token = parser.createToken(raw, node.startOffset, node.startLine, node.startCol); | ||
const psNode = { | ||
...token, | ||
type: 'psblock', | ||
depth: node.depth, | ||
nodeName: `#ps:${tag.type}`, | ||
parentNode: node.parentNode, | ||
childNodes: [], | ||
isBogus: false, | ||
}; | ||
if (node.type !== 'doctype' && node.parentNode?.childNodes) { | ||
parser.replaceChild(node.parentNode, node, psNode); | ||
} | ||
const index = newNodeList.indexOf(node); | ||
newNodeList.splice(index, 1, psNode); | ||
tag.resolved = true; | ||
} | ||
for (const node of newNodeList) { | ||
if (node.type === 'starttag') { | ||
for (const attr of node.attributes) { | ||
if (attr.type === 'ps-attr' || attr.value.raw === '' || !hasIgnoreBlock(attr.value.raw, maskChar)) { | ||
if (attr.type === 'spread' || attr.value.raw === '' || !hasIgnoreBlock(attr.value.raw, maskChar)) { | ||
continue; | ||
@@ -177,10 +102,35 @@ } | ||
const below = attr.value.raw.slice(offset + length); | ||
attr.value.raw = above + raw + below; | ||
attr.isDynamicValue = true; | ||
parser.updateRaw(attr.value, above + raw + below); | ||
parser.updateAttr(attr, { isDynamicValue: true }); | ||
tag.resolved = true; | ||
} | ||
parser.updateRaw(attr, attr.name.raw + | ||
attr.spacesBeforeEqual.raw + | ||
attr.equal.raw + | ||
attr.spacesAfterEqual.raw + | ||
attr.startQuote.raw + | ||
attr.value.raw + | ||
attr.endQuote.raw); | ||
} | ||
// Update node raw | ||
const length = attr.raw.length; | ||
const offset = attr.startOffset - node.startOffset; | ||
const above = node.raw.slice(0, offset); | ||
const below = node.raw.slice(offset + length); | ||
parser.updateRaw(node, above + attr.raw + below); | ||
} | ||
} | ||
} | ||
return nodeList; | ||
if (throwErrorWhenTagHasUnresolved) { | ||
for (const tag of stack) { | ||
if (!tag.resolved) { | ||
throw new ParserError('Parsing failed. Unsupported syntax detected', { | ||
line: getLine(source, tag.index), | ||
col: getCol(source, tag.index), | ||
raw: tag.startTag + tag.taggedCode + (tag.endTag ?? ''), | ||
}); | ||
} | ||
} | ||
} | ||
return newNodeList; | ||
} | ||
@@ -204,14 +154,2 @@ function snap(str, reg) { | ||
} | ||
function prepend(reg, str) { | ||
if (typeof reg === 'string') { | ||
return new RegExp(str + escapeRegExpForStr(reg)); | ||
} | ||
return new RegExp(str + reg.source, reg.ignoreCase ? 'i' : ''); | ||
} | ||
function append(reg, str) { | ||
if (typeof reg === 'string') { | ||
return new RegExp(escapeRegExpForStr(reg) + str); | ||
} | ||
return new RegExp(reg.source + str, reg.ignoreCase ? 'i' : ''); | ||
} | ||
function hasIgnoreBlock(textContent, maskChar) { | ||
@@ -218,0 +156,0 @@ return textContent.includes(maskChar); |
@@ -1,1 +0,4 @@ | ||
export declare function ignoreFrontMatter(code: string): string; | ||
export declare function ignoreFrontMatter(code: string): { | ||
code: string; | ||
frontMatter: string | null; | ||
}; |
export function ignoreFrontMatter(code) { | ||
const reStart = /^(?:\s*\n)?---\r?\n/.exec(code); | ||
if (!reStart) { | ||
return code; | ||
return { | ||
code, | ||
frontMatter: null, | ||
}; | ||
} | ||
@@ -10,3 +13,6 @@ const startPoint = reStart[0].length; | ||
if (!reEnd) { | ||
return code; | ||
return { | ||
code, | ||
frontMatter: null, | ||
}; | ||
} | ||
@@ -17,3 +23,6 @@ const endPoint = startPoint + reEnd.index + reEnd[0].length; | ||
const masked = frontMatter.replaceAll(/[^\n\r]/g, ' '); | ||
return masked + afterCode; | ||
return { | ||
code: masked + afterCode, | ||
frontMatter, | ||
}; | ||
} |
@@ -1,20 +0,7 @@ | ||
export * from './attr-parser.js'; | ||
export * from './attr-tokenizer.js'; | ||
export * from './const.js'; | ||
export * from './create-token.js'; | ||
export * from './debugger.js'; | ||
export * from './decision.js'; | ||
export * from './detect-element-type.js'; | ||
export * from './flatten-nodes.js'; | ||
export * from './get-location.js'; | ||
export * from './get-space-before.js'; | ||
export * from './enums.js'; | ||
export * from './idl-attributes.js'; | ||
export * from './ignore-block.js'; | ||
export * from './ignore-front-matter.js'; | ||
export * from './parse-attr.js'; | ||
export * from './parser-error.js'; | ||
export * from './remove-deprecated-node.js'; | ||
export * from './parser.js'; | ||
export * from './script-parser.js'; | ||
export * from './tag-parser.js'; | ||
export * from './tag-splitter.js'; | ||
export * from './walker.js'; | ||
export * from './types.js'; |
@@ -1,20 +0,7 @@ | ||
export * from './attr-parser.js'; | ||
export * from './attr-tokenizer.js'; | ||
export * from './const.js'; | ||
export * from './create-token.js'; | ||
export * from './debugger.js'; | ||
export * from './decision.js'; | ||
export * from './detect-element-type.js'; | ||
export * from './flatten-nodes.js'; | ||
export * from './get-location.js'; | ||
export * from './get-space-before.js'; | ||
export * from './enums.js'; | ||
export * from './idl-attributes.js'; | ||
export * from './ignore-block.js'; | ||
export * from './ignore-front-matter.js'; | ||
export * from './parse-attr.js'; | ||
export * from './parser-error.js'; | ||
export * from './remove-deprecated-node.js'; | ||
export * from './parser.js'; | ||
export * from './script-parser.js'; | ||
export * from './tag-parser.js'; | ||
export * from './tag-splitter.js'; | ||
export * from './walker.js'; | ||
export * from './types.js'; |
@@ -5,2 +5,3 @@ export type ParserErrorInfo = { | ||
readonly raw?: string; | ||
readonly stack?: string; | ||
}; | ||
@@ -7,0 +8,0 @@ export declare class ParserError extends Error { |
@@ -8,2 +8,3 @@ export class ParserError extends Error { | ||
this.raw = info.raw ?? ''; | ||
this.stack = info.stack ?? this.stack; | ||
} | ||
@@ -10,0 +11,0 @@ } |
@@ -1,2 +0,2 @@ | ||
export declare function scriptParser(script: string): any; | ||
export declare function scriptParser(script: string): ScriptTokenType[]; | ||
export declare function removeQuote(str: string): string; | ||
@@ -3,0 +3,0 @@ export type ScriptTokenType = { |
@@ -15,3 +15,3 @@ // @ts-ignore | ||
const quote = str[0]; | ||
if (quote !== '"' && quote !== "'") { | ||
if (quote !== '"' && quote !== "'" && quote !== '`') { | ||
return str; | ||
@@ -18,0 +18,0 @@ } |
@@ -0,1 +1,34 @@ | ||
import type { EndTagType, MLASTParentNode, ParserOptions as ConfigParserOptions } from '@markuplint/ml-ast'; | ||
export type ParserOptions = { | ||
readonly booleanish?: boolean; | ||
readonly endTagType?: EndTagType; | ||
readonly ignoreTags?: readonly IgnoreTag[]; | ||
readonly maskChar?: string; | ||
readonly tagNameCaseSensitive?: boolean; | ||
readonly selfCloseType?: SelfCloseType; | ||
readonly spaceChars?: readonly string[]; | ||
readonly rawTextElements?: readonly string[]; | ||
}; | ||
export type ParseOptions = ConfigParserOptions & { | ||
readonly offsetOffset?: number; | ||
readonly offsetLine?: number; | ||
readonly offsetColumn?: number; | ||
readonly depth?: number; | ||
}; | ||
export type Tokenized<N extends {} = {}, State extends unknown = null> = { | ||
readonly ast: N[]; | ||
readonly isFragment: boolean; | ||
readonly state?: State; | ||
}; | ||
export type Token = { | ||
readonly raw: string; | ||
readonly startOffset: number; | ||
readonly startLine: number; | ||
readonly startCol: number; | ||
}; | ||
export type ChildToken = Token & { | ||
readonly depth: number; | ||
readonly parentNode: MLASTParentNode | null; | ||
}; | ||
export type SelfCloseType = 'html' | 'xml' | 'html+xml'; | ||
export type Code = { | ||
@@ -7,2 +40,3 @@ readonly type: string; | ||
readonly endTag: string | null; | ||
resolved: boolean; | ||
}; | ||
@@ -9,0 +43,0 @@ export type IgnoreTag = { |
{ | ||
"name": "@markuplint/parser-utils", | ||
"version": "4.0.0-dev.10+b28398ab", | ||
"version": "4.0.0-dev.12+2275fbeb0", | ||
"description": "Utility module for markuplint parser plugin", | ||
@@ -13,2 +13,5 @@ "repository": "git@github.com:markuplint/markuplint.git", | ||
"import": "./lib/index.js" | ||
}, | ||
"./location": { | ||
"import": "./lib/get-location.js" | ||
} | ||
@@ -28,10 +31,11 @@ }, | ||
"dependencies": { | ||
"@markuplint/ml-ast": "4.0.0-dev.10+b28398ab", | ||
"@markuplint/types": "4.0.0-dev.10+b28398ab", | ||
"@markuplint/ml-ast": "4.0.0-dev.12+2275fbeb0", | ||
"@markuplint/ml-spec": "4.0.0-dev.12+2275fbeb0", | ||
"@markuplint/types": "4.0.0-dev.12+2275fbeb0", | ||
"@types/uuid": "^9.0.7", | ||
"espree": "^9.6.1", | ||
"type-fest": "^4.8.3", | ||
"type-fest": "^4.9.0", | ||
"uuid": "^9.0.1" | ||
}, | ||
"gitHead": "b28398ab9c8f0ad790f2915ad5da8f3a80e9b8d6" | ||
"gitHead": "2275fbeb053605b636f080f4fafd7cd4fc57a9a3" | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
95817
2486
7
35
2