@criticalmanufacturing/dev-i18n-transform
Version:
i18n <--> gettext transform
168 lines • 9.88 kB
JavaScript
;
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