UNPKG

@criticalmanufacturing/dev-i18n-transform

Version:
168 lines 9.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); const path = require("path"); const index_1 = require("../logger/index"); const index_2 = require("../validators/index"); const file_1 = require("../model/file"); const message_1 = require("../model/message"); const translation_1 = require("../model/translation"); const package_1 = require("../model/package"); const util_1 = require("../util"); class TypescriptParser { /** * Typescript File(s) Analyser * @param packagePath Absolute package path * @param filePaths File paths to analyse */ constructor(packagePath, filePaths) { /** * Typescript messages */ this._messages = []; this.fileParse = (node) => { // Each file should start with an export assignment. // When we find a node of that kind, it means we have a new file in our structure. // If the typescript file imports any other file, let's save that reference for later reuse. // After, we move to parse the remaining file nodes. switch (node.kind) { case ts.SyntaxKind.ExportAssignment: let tsSourceFile = node.getSourceFile(); let file = new file_1.File(tsSourceFile.fileName, this._packagePath); // Add references for (let ref of tsSourceFile.imports) { file.addOrUpdateReference(ref.parent.getText()); } let identifierPath = []; let nodeParser = (node) => { index_1.default.info("node", { kind: ts.SyntaxKind[node.kind], text: node.getFullText(), name: node.name }); switch (node.kind) { case ts.SyntaxKind.Identifier: break; case ts.SyntaxKind.StringLiteral: break; case ts.SyntaxKind.PropertyAssignment: // When we find a PropertyAssignment node like // #Comment // 'propertyIdentifier: propertyInitializer' let paNode = node; let messageDescription = null; // Check if the property as any comment associated let symbol = this._typeChecker.getSymbolAtLocation(paNode.name); if (symbol != null) { messageDescription = ts.displayPartsToString(symbol.getDocumentationComment(undefined)); } // Find node bearing // From one property to the next, we need to check if we are on the correct // depth of our tree structure for (let i = identifierPath.length - 1; i >= 0; i--) { if (identifierPath[i].node === node.parent.parent) { break; } identifierPath.pop(); } // Depending on the property initializer, we have to handle it differently switch (paNode.initializer.kind) { // If we have a string literal (ex: property1: "Property1Value") // Or if we have a template expression (ex: property1: `This is a template string ${i18nControls.LABEL}`) // We need to remove the first and last char, get the line and char position, and get the translation case ts.SyntaxKind.StringLiteral: case ts.SyntaxKind.TemplateExpression: { let nodeText = paNode.initializer.getText(); nodeText = nodeText.slice(1, -1); let { line, character } = tsSourceFile.getLineAndCharacterOfPosition(paNode.initializer.getStart()); let message = new message_1.Message(identifierPath.map((id) => id.name).concat([paNode.name.getText().replace(/^["]+|["]+$/g, '')]).join("."), messageDescription); message.addOrUpdateTranslation(new translation_1.Translation(this._currentLanguage, nodeText, false, line, character)); file.addOrUpdateMessage(message); } break; // If we have a property access (ex: property1: i18nControls.LABEL) // Or if we have a binary expression (ex: property1: i18nControls.LABEL + "abc") // We just use that value as the translation itself case ts.SyntaxKind.Identifier: case ts.SyntaxKind.BinaryExpression: case ts.SyntaxKind.PropertyAccessExpression: { let nodeText = paNode.initializer.getText(); let { line, character } = tsSourceFile.getLineAndCharacterOfPosition(paNode.initializer.getStart()); let message = new message_1.Message(identifierPath.map((id) => id.name).concat([paNode.name.getText().replace(/^["]+|["]+$/g, '')]).join("."), messageDescription); message.addOrUpdateTranslation(new translation_1.Translation(this._currentLanguage, nodeText, true, line, character)); file.addOrUpdateMessage(message); } break; // If we have an object literal (ex: property1: {...}) // We create a new node on our tree, and move to parse the object case ts.SyntaxKind.ObjectLiteralExpression: identifierPath.push({ name: paNode.name.getText().replace(/^["]+|["]+$/g, ''), node: paNode }); break; // Else, we log a warning for future reference and continue default: index_1.default.warn("Unhandled property identifier", { kind: ts.SyntaxKind[paNode.initializer.kind], node: paNode }); break; } break; default: break; } ts.forEachChild(node, nodeParser); }; nodeParser(node); this._files.push(file); break; } }; this._filePaths = filePaths.map(path.normalize); this._packagePath = path.normalize(packagePath); this._program = ts.createProgram(filePaths, { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.System, rootDir: process.cwd() }); this._typeChecker = this._program.getTypeChecker(); } /** * Run the typescript parser. * @returns A fully loaded package, describing all i18n files of the package. */ run() { index_1.default.info(`Starting Typescript parse`, { package: this._packagePath, paths: this._filePaths }); this._files = []; for (const sourceFile of this._program.getSourceFiles()) { let sourceFilename = path.normalize(sourceFile.fileName); if (sourceFilename.indexOf("node_modules\\") < 0 && sourceFilename.indexOf(this._packagePath) >= 0) { let filename = path.basename(sourceFilename); let match = file_1.File.parseFileName(filename); this._fileName = match.name; this._currentLanguage = match.language === "default" ? util_1.default.defaultLanguage : match.language; ts.forEachChild(sourceFile, this.fileParse); } } let pack = new package_1.Package(this._packagePath); for (let file of this._files) { pack.addOrUpdateFile(file); } // Validate package let validationResults = index_2.ValidatorFactory.validate(pack); validationResults.forEach((validationResult) => { index_1.default.warn(validationResult.message, { file: validationResult.file.uniqueFileName, line: validationResult.line, col: validationResult.col }); }); return pack; } } exports.TypescriptParser = TypescriptParser; //# sourceMappingURL=typescript.parser.js.map