@intlayer/chokidar
Version:
Uses chokidar to scan and build Intlayer declaration files into dictionaries based on Intlayer configuration.
125 lines (123 loc) • 5.12 kB
JavaScript
import * as recast from "recast";
//#region src/writeContentDeclaration/transformJSONFile.ts
const b = recast.types.builders;
const n = recast.types.namedTypes;
/**
* Checks if a value is a plain object (and not null/array)
*/
const isPlainObject = (value) => {
return typeof value === "object" && value !== null && !Array.isArray(value);
};
/**
* Checks if a recast AST node value matches the incoming primitive value.
*/
const isPrimitiveEqual = (astNode, val) => {
if (val === null) return n.Literal.check(astNode) && astNode.value === null;
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") return (n.Literal.check(astNode) || n.StringLiteral.check(astNode) || n.NumericLiteral.check(astNode) || n.BooleanLiteral.check(astNode)) && astNode.value === val;
return false;
};
/**
* Robustly finds a property in a recast ObjectExpression.
* Handles quoted ("key") or unquoted (key) properties.
*/
const getMatchingProperty = (node, key) => {
return node.properties.find((prop) => {
if (n.Property.check(prop) || n.ObjectProperty.check(prop)) {
if (n.Identifier.check(prop.key) && prop.key.name === key) return true;
if (n.StringLiteral.check(prop.key) && prop.key.value === key) return true;
if (n.Literal.check(prop.key) && prop.key.value === key) return true;
}
return false;
});
};
/**
* Recursively builds a clean recast AST node from a plain JS value.
* Because these nodes lack `loc` tracking, recast is forced to pretty-print them.
*/
const buildASTNode = (val) => {
if (val === null) return b.literal(null);
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") return b.literal(val);
if (Array.isArray(val)) return b.arrayExpression(val.map((item) => buildASTNode(item)));
if (isPlainObject(val)) return b.objectExpression(Object.entries(val).filter(([, v]) => v !== void 0).map(([k, v]) => b.property("init", b.stringLiteral(k), buildASTNode(v))));
return b.literal(null);
};
/**
* Recursively updates the AST object literal with new data.
*/
const updateObjectLiteral = (node, data) => {
for (const [key, val] of Object.entries(data)) {
if (val === void 0) continue;
const existingProp = getMatchingProperty(node, key);
if (existingProp?.comments) existingProp.comments.forEach((c) => {
if ((c.type === "Line" || c.type === "CommentLine") && c.trailing) {
c.type = c.type === "Line" ? "Block" : "CommentBlock";
c.value = `__INLINE_LINE__${c.value}`;
c.leading = false;
c.trailing = true;
}
});
if (isPlainObject(val)) if (existingProp && n.ObjectExpression.check(existingProp.value)) updateObjectLiteral(existingProp.value, val);
else if (existingProp) {
if (!(n.Literal.check(existingProp.value) && existingProp.value.value === val)) existingProp.value = buildASTNode(val);
} else node.properties.push(b.property("init", b.stringLiteral(key), buildASTNode(val)));
else if (existingProp) {
if (!isPrimitiveEqual(existingProp.value, val)) existingProp.value = buildASTNode(val);
} else node.properties.push(b.property("init", b.stringLiteral(key), buildASTNode(val)));
}
};
const transformJSONFile = (fileContent, dictionary, noMetadata) => {
const wrappedContent = `const _config = ${fileContent.trim() || "{}"};`;
let ast;
try {
ast = recast.parse(wrappedContent);
} catch {
return JSON.stringify(noMetadata ? dictionary.content : dictionary, null, 2);
}
const declaration = ast.program.body[0];
let objectLiteral;
if (n.VariableDeclaration.check(declaration) && declaration.declarations.length > 0 && n.VariableDeclarator.check(declaration.declarations[0]) && n.ObjectExpression.check(declaration.declarations[0].init)) objectLiteral = declaration.declarations[0].init;
if (noMetadata) {
const metadataProperties = [
"id",
"locale",
"filled",
"fill",
"title",
"description",
"tags",
"version",
"priority",
"contentAutoTransformation",
"$schema"
];
for (let i = objectLiteral.properties.length - 1; i >= 0; i--) {
const prop = objectLiteral.properties[i];
if (n.Property.check(prop) || n.ObjectProperty.check(prop)) {
let propName = "";
if (n.Identifier.check(prop.key)) propName = prop.key.name;
else if (n.StringLiteral.check(prop.key)) propName = prop.key.value;
else if (n.Literal.check(prop.key)) propName = String(prop.key.value);
if ([
"key",
"content",
...metadataProperties
].includes(propName)) objectLiteral.properties.splice(i, 1);
}
}
}
updateObjectLiteral(objectLiteral, noMetadata ? dictionary.content : dictionary);
const printedCode = recast.print(ast, {
tabWidth: 2,
quote: "double",
trailingComma: false
}).code;
const startIndex = printedCode.indexOf("{");
const endIndex = printedCode.lastIndexOf("}");
let finalOutput = printedCode.substring(startIndex, endIndex + 1);
finalOutput = finalOutput.replace(/\s*\/\*__INLINE_LINE__(.*?)\*\/(\s*,?)/g, "$2 //$1");
finalOutput = finalOutput.replace(/\n[ \t]*\n/g, "\n");
return finalOutput;
};
//#endregion
export { transformJSONFile };
//# sourceMappingURL=transformJSONFile.mjs.map