Skip to content
This repository was archived by the owner on Apr 19, 2024. It is now read-only.

allow es imports, update ts and use their helper methods #27

Merged
merged 3 commits into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@types/iconv-lite": "^0.0.1",
"@types/minimatch": "^3.0.3",
"@types/mocha": "^7.0.2",
"@types/node": "^9.3.0",
"@types/node": "^12.0.0",
"@types/source-map": "^0.5.7",
"@types/through": "^0.0.29",
"@types/vinyl": "^2.0.2",
Expand All @@ -42,7 +42,7 @@
"iconv-lite": "^0.4.19",
"is": "^3.2.1",
"source-map": "^0.6.1",
"typescript": "^2.6.2",
"typescript": "^3.9.5",
"vinyl": "^2.1.0",
"xml2js": "^0.4.19",
"yargs": "^13.2.4"
Expand Down
113 changes: 51 additions & 62 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,27 @@ function analyze(contents: string, relativeFilename: string, options: ts.Compile
}

function isImportNode(node: ts.Node): boolean {
return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration;
if (ts.isImportDeclaration(node)) {
return ts.isStringLiteralLike(node.moduleSpecifier) && vscodeRegExp.test(node.moduleSpecifier.getText());
}

if (ts.isImportEqualsDeclaration(node)) {
return ts.isExternalModuleReference(node.moduleReference)
&& ts.isStringLiteralLike(node.moduleReference.expression)
&& vscodeRegExp.test(node.moduleReference.expression.getText());
}
}

function isRequireImport(node: ts.Node): boolean {
if (node.kind !== ts.SyntaxKind.CallExpression) {
if (!ts.isCallExpression(node)) {
return false;
}
const callExpression = node as ts.CallExpression;
if (callExpression.expression.getText() !== 'require' || !callExpression.arguments || callExpression.arguments.length !== 1) {

if (node.expression.getText() !== 'require' || !node.arguments || node.arguments.length !== 1) {
return false;
}
const argument = callExpression.arguments[0];
return argument.kind === ts.SyntaxKind.StringLiteral && vscodeRegExp.test(argument.getText());
const argument = node.arguments[0];
return ts.isStringLiteralLike(argument) && vscodeRegExp.test(argument.getText());
}

function findClosestNode(node: ts.Node, textSpan: ts.TextSpan): ts.Node {
Expand All @@ -407,38 +415,6 @@ function analyze(contents: string, relativeFilename: string, options: ts.Compile
return loop(node);
}

function isIdentifier(node: ts.Node): node is ts.Identifier {
return node && node.kind === ts.SyntaxKind.Identifier;
}

function isVariableDeclaration(node: ts.Node): node is ts.VariableDeclaration {
return node && node.kind === ts.SyntaxKind.VariableDeclaration;
}

function isCallExpression(node: ts.Node): node is ts.CallExpression {
return node && node.kind === ts.SyntaxKind.CallExpression;
}

function isPropertyAccessExpression(node: ts.Node): node is ts.PropertyAccessExpression {
return node && node.kind === ts.SyntaxKind.PropertyAccessExpression;
}

function isStringLiteral(node: ts.Node): node is ts.StringLiteral {
return node && node.kind === ts.SyntaxKind.StringLiteral;
}

function isObjectLiteral(node: ts.Node): node is ts.ObjectLiteralExpression {
return node && node.kind === ts.SyntaxKind.ObjectLiteralExpression;
}

function isArrayLiteralExpression(node: ts.Node): node is ts.ArrayLiteralExpression {
return node && node.kind === ts.SyntaxKind.ArrayLiteralExpression;
}

function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
return node && node.kind === ts.SyntaxKind.PropertyAssignment;
}

const unescapeMap: Map<string> = {
'\'': '\'',
'"': '"',
Expand Down Expand Up @@ -483,40 +459,53 @@ function analyze(contents: string, relativeFilename: string, options: ts.Compile
const bundle: JavaScriptMessageBundle = { messages: [], keys: [] };

// all imports
const imports = collect(sourceFile, n => isRequireImport(n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse);
const imports = collect(sourceFile, n => isRequireImport(n) || isImportNode(n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse);

const nlsReferences = imports.reduce<ts.Node[]>((memo, node) => {
if (node.kind === ts.SyntaxKind.CallExpression) {
let references: ts.ReferenceEntry[] = [];

if (ts.isCallExpression(node)) {
let parent = node.parent;
if (isVariableDeclaration(parent)) {
let references = service.getReferencesAtPosition(filename, parent.name.pos + 1);
references.forEach(reference => {
if (!reference.isWriteAccess) {
let node = findClosestNode(sourceFile, reference.textSpan)
memo.push(node);
}
});
if (ts.isCallExpression(parent) && ts.isIdentifier(parent.expression) && parent.expression.text === '__importStar') {
parent = node.parent.parent;
}
if (ts.isVariableDeclaration(parent)) {
references = service.getReferencesAtPosition(filename, parent.name.pos + 1);
}
} else if (ts.isImportDeclaration(node)) {
if (ts.isNamespaceImport(node.importClause?.namedBindings)) {
references = service.getReferencesAtPosition(filename, node.importClause.namedBindings.pos);
}
} else if (ts.isImportEqualsDeclaration(node)) {
references = service.getReferencesAtPosition(filename, node.name.pos);
}

references.forEach(reference => {
if (!reference.isWriteAccess) {
let node = findClosestNode(sourceFile, reference.textSpan)
memo.push(node);
}
});

return memo;
}, []);

const loadCalls = nlsReferences.reduce<ts.CallExpression[]>((memo, node) => {
// We are looking for nls.loadMessageBundle || nls.config. In the AST
// this is Indetifier -> PropertyAccess -> CallExpression.
if (!isIdentifier(node) || !isPropertyAccessExpression(node.parent) || !isCallExpression(node.parent.parent)) {
if (!ts.isIdentifier(node) || !ts.isPropertyAccessExpression(node.parent) || !ts.isCallExpression(node.parent.parent)) {
return memo;
}
let callExpression = node.parent.parent;
let expression = callExpression.expression;
if (isPropertyAccessExpression(expression)) {
if (ts.isPropertyAccessExpression(expression)) {
if (expression.name.text === 'loadMessageBundle') {
// We have a load call like nls.loadMessageBundle();
memo.push(callExpression);
} else if (expression.name.text === 'config') {
// We have a load call like nls.config({...})();
let parent = callExpression.parent;
if (isCallExpression(parent) && parent.expression === callExpression) {
if (ts.isCallExpression(parent) && parent.expression === callExpression) {
memo.push(parent);
}
}
Expand All @@ -526,17 +515,17 @@ function analyze(contents: string, relativeFilename: string, options: ts.Compile

const localizeCalls = loadCalls.reduce<ts.CallExpression[]>((memo, loadCall) => {
let parent = loadCall.parent;
if (isCallExpression(parent)) {
if (ts.isCallExpression(parent)) {
// We have something like nls.config({...})()('key', 'message');
memo.push(parent);
} else if (isVariableDeclaration(parent)) {
} else if (ts.isVariableDeclaration(parent)) {
// We have something like var localize = nls.config({...})();
service.getReferencesAtPosition(filename, parent.name.pos + 1).forEach(reference => {
if (!reference.isWriteAccess) {
let node = findClosestNode(sourceFile, reference.textSpan);
if (isIdentifier(node)) {
if (ts.isIdentifier(node)) {
let parent = node.parent;
if (isCallExpression(parent) && parent.arguments.length >= 2) {
if (ts.isCallExpression(parent) && parent.arguments.length >= 2) {
memo.push(parent);
} else {
let position = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
Expand Down Expand Up @@ -568,25 +557,25 @@ function analyze(contents: string, relativeFilename: string, options: ts.Compile
let message: string = null;
let comment: string[] = [];
let text: string = null;
if (isStringLiteral(firstArg)) {
if (ts.isStringLiteralLike(firstArg)) {
text = firstArg.getText();
key = text.substr(1, text.length - 2);
} else if (isObjectLiteral(firstArg)) {
} else if (ts.isObjectLiteralExpression(firstArg)) {
for (let i = 0; i < firstArg.properties.length; i++) {
let property = firstArg.properties[i];
if (isPropertyAssignment(property)) {
if (ts.isPropertyAssignment(property)) {
let name = property.name.getText();
if (name === 'key') {
let initializer = property.initializer;
if (isStringLiteral(initializer)) {
if (ts.isStringLiteralLike(initializer)) {
text = initializer.getText();
key = text.substr(1, text.length - 2);
}
} else if (name === 'comment') {
let initializer = property.initializer;
if (isArrayLiteralExpression(initializer)) {
if (ts.isArrayLiteralExpression(initializer)) {
initializer.elements.forEach(element => {
if (isStringLiteral(element)) {
if (ts.isStringLiteralLike(element)) {
text = element.getText();
comment.push(text.substr(1, text.length - 2));
}
Expand All @@ -601,7 +590,7 @@ function analyze(contents: string, relativeFilename: string, options: ts.Compile
errors.push(`(${position.line + 1},${position.character + 1}): first argument of a localize call must either be a string literal or an object literal of type LocalizeInfo.`);
return memo;
}
if (isStringLiteral(secondArg)) {
if (ts.isStringLiteralLike(secondArg)) {
let text = secondArg.getText();
message = text.substr(1, text.length - 2);
}
Expand Down
46 changes: 46 additions & 0 deletions src/tests/analyze.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,52 @@ describe('Localize', () => {
assert.strictEqual(result.contents, expected.join('\n'));
})

it('works with es imports', () => {
let code: string[] = [
"import * as nls from 'vscode-nls';",
"var localize = nls.loadMessageBundle();",
"localize('keyOne', '{0} {1}', 'Hello', 'World');"
];
let result = nlsDev.processFile(code.join('\n'), 'foo.js');
let expected: string[] = [
"import * as nls from 'vscode-nls';",
"var localize = nls.loadMessageBundle(require('path').join(__dirname, 'foo.js'));",
"localize(0, null, 'Hello', 'World');"
];
assert.strictEqual(result.contents, expected.join('\n'));
})

it('works with import equals', () => {
let code: string[] = [
"import nls = require('vscode-nls')",
"var localize = nls.loadMessageBundle();",
"localize('keyOne', '{0} {1}', 'Hello', 'World');"
];
let result = nlsDev.processFile(code.join('\n'), 'foo.js');
let expected: string[] = [
"import nls = require('vscode-nls')",
"var localize = nls.loadMessageBundle(require('path').join(__dirname, 'foo.js'));",
"localize(0, null, 'Hello', 'World');"
];
assert.strictEqual(result.contents, expected.join('\n'));
})

it('works with compiled __importStar', () => {
let code: string[] = [
"const nls = __importStar(require('vscode-nls'))",
"var localize = nls.loadMessageBundle();",
"localize('keyOne', '{0} {1}', 'Hello', 'World');"
];
let result = nlsDev.processFile(code.join('\n'), 'foo.js');
let expected: string[] = [
"const nls = __importStar(require('vscode-nls'))",
"var localize = nls.loadMessageBundle(require('path').join(__dirname, 'foo.js'));",
"localize(0, null, 'Hello', 'World');"
];
assert.strictEqual(result.contents, expected.join('\n'));
})


it('https://github.com/Microsoft/vscode/issues/56792', () => {
let code: string[] = [
"var nls = require('vscode-nls');",
Expand Down