UNPKG

@intlayer/chokidar

Version:

Scans and builds Intlayer declaration files into dictionaries based on Intlayer configuration.

1 lines 10.4 kB
{"version":3,"file":"transformJSONFile.cjs","names":[],"sources":["../../../src/writeContentDeclaration/transformJSONFile.ts"],"sourcesContent":["import type { Dictionary } from '@intlayer/types/dictionary';\nimport * as recast from 'recast';\n\nconst b = recast.types.builders;\nconst n = recast.types.namedTypes;\n\n/**\n * Checks if a value is a plain object (and not null/array)\n */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n};\n\n/**\n * Checks if a recast AST node value matches the incoming primitive value.\n */\nconst isPrimitiveEqual = (astNode: any, val: unknown): boolean => {\n if (val === null) {\n return n.Literal.check(astNode) && astNode.value === null;\n }\n if (\n typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean'\n ) {\n return (\n (n.Literal.check(astNode) ||\n n.StringLiteral.check(astNode) ||\n n.NumericLiteral.check(astNode) ||\n n.BooleanLiteral.check(astNode)) &&\n astNode.value === val\n );\n }\n return false;\n};\n\n/**\n * Robustly finds a property in a recast ObjectExpression.\n * Handles quoted (\"key\") or unquoted (key) properties.\n */\nconst getMatchingProperty = (node: any, key: string) => {\n return node.properties.find((prop: any) => {\n if (n.Property.check(prop) || n.ObjectProperty.check(prop)) {\n if (n.Identifier.check(prop.key) && prop.key.name === key) return true;\n if (n.StringLiteral.check(prop.key) && prop.key.value === key)\n return true;\n if (n.Literal.check(prop.key) && prop.key.value === key) return true;\n }\n return false;\n });\n};\n\n/**\n * Recursively builds a clean recast AST node from a plain JS value.\n * Because these nodes lack `loc` tracking, recast is forced to pretty-print them.\n */\nconst buildASTNode = (val: unknown): any => {\n if (val === null) return b.literal(null);\n\n if (\n typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean'\n ) {\n return b.literal(val);\n }\n\n if (Array.isArray(val)) {\n return b.arrayExpression(val.map((item) => buildASTNode(item)));\n }\n\n if (isPlainObject(val)) {\n return b.objectExpression(\n Object.entries(val)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) =>\n b.property('init', b.stringLiteral(k), buildASTNode(v))\n )\n );\n }\n\n return b.literal(null); // Fallback\n};\n\n/**\n * Recursively updates the AST object literal with new data.\n */\nconst updateObjectLiteral = (node: any, data: Record<string, any>) => {\n for (const [key, val] of Object.entries(data)) {\n if (val === undefined) {\n continue;\n }\n\n const existingProp = getMatchingProperty(node, key);\n\n if (existingProp?.comments) {\n existingProp.comments.forEach((c: any) => {\n if ((c.type === 'Line' || c.type === 'CommentLine') && c.trailing) {\n // Convert to block comment and tag it to force Recast to print it inline\n c.type = c.type === 'Line' ? 'Block' : 'CommentBlock';\n c.value = `__INLINE_LINE__${c.value}`;\n c.leading = false;\n c.trailing = true;\n }\n });\n }\n\n if (isPlainObject(val)) {\n if (existingProp && n.ObjectExpression.check(existingProp.value)) {\n updateObjectLiteral(existingProp.value, val);\n } else {\n if (existingProp) {\n // Prevent AST invalidation if the primitive value is identical\n const isIdentical =\n n.Literal.check(existingProp.value) &&\n existingProp.value.value === val;\n\n if (!isIdentical) {\n existingProp.value = buildASTNode(val);\n }\n } else {\n node.properties.push(\n b.property('init', b.stringLiteral(key), buildASTNode(val))\n );\n }\n }\n } else {\n // Handle primitives and arrays\n if (existingProp) {\n // Skip assignment if the primitive value is identical\n if (!isPrimitiveEqual(existingProp.value, val)) {\n existingProp.value = buildASTNode(val);\n }\n } else {\n node.properties.push(\n b.property('init', b.stringLiteral(key), buildASTNode(val))\n );\n }\n }\n }\n};\n\nexport const transformJSONFile = (\n fileContent: string,\n dictionary: Dictionary,\n noMetadata?: boolean\n): string => {\n // Wrap content to create valid syntax for the parser\n const wrappedContent = `const _config = ${fileContent.trim() || '{}'};`;\n\n let ast: ReturnType<typeof recast.parse>;\n try {\n ast = recast.parse(wrappedContent);\n } catch {\n // Fallback if parsing failed\n return JSON.stringify(\n noMetadata ? dictionary.content : dictionary,\n null,\n 2\n );\n }\n\n // Navigate the AST to locate the object literal initialized to `_config`\n const declaration = ast.program.body[0];\n let objectLiteral: any;\n\n if (\n n.VariableDeclaration.check(declaration) &&\n declaration.declarations.length > 0 &&\n n.VariableDeclarator.check(declaration.declarations[0]) &&\n n.ObjectExpression.check(declaration.declarations[0].init)\n ) {\n objectLiteral = declaration.declarations[0].init;\n }\n\n if (noMetadata) {\n const metadataProperties = [\n 'id',\n 'locale',\n 'filled',\n 'fill',\n 'title',\n 'description',\n 'tags',\n 'version',\n 'priority',\n 'contentAutoTransformation',\n '$schema',\n ];\n\n // Mutate the array backwards instead of using .filter() to prevent layout invalidation\n for (let i = objectLiteral.properties.length - 1; i >= 0; i--) {\n const prop = objectLiteral.properties[i];\n if (n.Property.check(prop) || n.ObjectProperty.check(prop)) {\n let propName = '';\n if (n.Identifier.check(prop.key)) propName = prop.key.name;\n else if (n.StringLiteral.check(prop.key)) propName = prop.key.value;\n else if (n.Literal.check(prop.key)) propName = String(prop.key.value);\n\n if (['key', 'content', ...metadataProperties].includes(propName)) {\n objectLiteral.properties.splice(i, 1);\n }\n }\n }\n }\n\n // Update the AST in place\n updateObjectLiteral(\n objectLiteral,\n noMetadata ? (dictionary.content as Record<string, any>) : dictionary\n );\n\n // Print the full AST to utilize the global token stream for inline comments\n const printedCode = recast.print(ast, {\n tabWidth: 2,\n quote: 'double',\n trailingComma: false,\n }).code;\n\n // Extract the target object literal cleanly\n const startIndex = printedCode.indexOf('{');\n const endIndex = printedCode.lastIndexOf('}');\n let finalOutput = printedCode.substring(startIndex, endIndex + 1);\n\n // Revert the tagged block comment back to an inline line comment and fix comma placement\n finalOutput = finalOutput.replace(\n /\\s*\\/\\*__INLINE_LINE__(.*?)\\*\\/(\\s*,?)/g,\n '$2 //$1'\n );\n\n // Collapse empty lines injected by Recast around commented properties\n finalOutput = finalOutput.replace(/\\n[ \\t]*\\n/g, '\\n');\n\n return finalOutput;\n};\n"],"mappings":"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"}