prettier-plugin-mp
Version:
Prettier plugin for WeChat Mini Program WXML files
172 lines (144 loc) • 4.78 kB
JavaScript
import { parse as wxmlParse } from "@wxml/parser";
// Protect wxs content from XML parser
function protectWxsContent(text, protectedItems) {
let wxsIndex = 0;
return text.replace(/(<wxs[^>]*>)([\s\S]*?)(<\/wxs>)/g, (match, openTag, content, closeTag) => {
const placeholder = `__WXS_CONTENT_${wxsIndex}__`;
protectedItems.push({
placeholder,
content: content.trim(),
type: 'WXS_CONTENT'
});
wxsIndex++;
return `${openTag}${placeholder}${closeTag}`;
});
}
// Protect template expressions from XML parser
function protectTemplateExpressions(text, protectedItems) {
let templateIndex = 0;
let result = '';
let i = 0;
while (i < text.length) {
// Look for opening {{
if (i < text.length - 1 && text[i] === '{' && text[i + 1] === '{') {
let start = i;
i += 2; // Skip {{
// Find the matching }}
let braceCount = 1;
let expressionStart = i;
while (i < text.length && braceCount > 0) {
if (text[i] === '{') {
braceCount++;
} else if (text[i] === '}') {
braceCount--;
if (braceCount === 0 && i < text.length - 1 && text[i + 1] === '}') {
// Found matching }}
const content = text.slice(expressionStart, i);
const placeholder = `__TEMPLATE_EXPR_${templateIndex}__`;
protectedItems.push({
placeholder,
content: content, // Keep original spacing
type: 'TEMPLATE_EXPR'
});
templateIndex++;
result += placeholder;
i += 2; // Skip }}
break;
}
}
i++;
}
// If we didn't find a matching }}, treat as regular text
if (braceCount > 0) {
result += text.slice(start, expressionStart);
i = expressionStart;
}
} else {
result += text[i];
i++;
}
}
return result;
}
// Preprocess text for XML parsing
function preprocessText(text) {
const protectedItems = [];
let processedText = protectWxsContent(text, protectedItems);
processedText = protectTemplateExpressions(processedText, protectedItems);
return { processedText, protectedItems };
}
// Restore protected content in AST
// Restore a single string property
function restoreStringProperty(str, protectedItems) {
if (!str || typeof str !== 'string') return str;
protectedItems.forEach(({ placeholder, content, type }) => {
if (str.includes(placeholder)) {
if (type === 'WXS_CONTENT') {
str = str.replaceAll(placeholder, content);
} else if (type === 'TEMPLATE_EXPR') {
str = str.replaceAll(placeholder, `{{${content}}}`);
}
}
});
return str;
}
// Restore attribute values
function restoreAttributeValue(value, protectedItems) {
if (!value || typeof value !== 'string') return value;
for (const { placeholder, content, type } of protectedItems) {
if (value.includes(placeholder)) {
if (type === 'WXS_CONTENT') {
value = value.replaceAll(placeholder, content);
} else if (type === 'TEMPLATE_EXPR') {
value = value.replaceAll(placeholder, `{{${content}}}`);
}
}
}
return value;
}
// Recursively restore protected content in AST
function restoreProtectedContent(node, protectedItems) {
if (!node || typeof node !== 'object') return node;
if (Array.isArray(node)) {
return node.map(child => restoreProtectedContent(child, protectedItems));
}
// Restore string properties
if (node.image) {
node.image = restoreStringProperty(node.image, protectedItems);
}
// Restore attribute values
if (node.value !== undefined) {
node.value = restoreAttributeValue(node.value, protectedItems);
}
if (node.rawValue !== undefined) {
node.rawValue = restoreAttributeValue(node.rawValue, protectedItems);
}
// Recursively process all object properties
Object.keys(node).forEach(key => {
if (typeof node[key] === 'object') {
node[key] = restoreProtectedContent(node[key], protectedItems);
}
});
return node;
}
const parser = {
parse(text) {
// Preprocess text for XML parsing
const { processedText, protectedItems } = preprocessText(text);
// Parse with @wxml/parser
let ast = wxmlParse(processedText);
// Restore protected content
ast = restoreProtectedContent(ast, protectedItems);
// Add comment tokens to AST for ignore functionality
ast.commentTokens = ast.comments || [];
return ast;
},
astFormat: "wxml",
locStart(node) {
return node.location ? node.location.startOffset : 0;
},
locEnd(node) {
return node.location ? node.location.endOffset : 0;
}
};
export default parser;