ts-simple-ast
Version:
TypeScript compiler wrapper for static analysis and code manipulation.
403 lines (402 loc) • 23.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var errors = require("../../errors");
var fileSystem_1 = require("../../fileSystem");
var manipulation_1 = require("../../manipulation");
var typescript_1 = require("../../typescript");
var utils_1 = require("../../utils");
var Program_1 = require("./Program");
var results_1 = require("./results");
var LanguageService = /** @class */ (function () {
/** @private */
function LanguageService(context) {
var _this = this;
this._context = context;
var version = 0;
var fileExistsSync = function (path) { return _this._context.compilerFactory.containsSourceFileAtPath(path) || context.fileSystemWrapper.fileExistsSync(path); };
var languageServiceHost = {
getCompilationSettings: function () { return context.compilerOptions.get(); },
getNewLine: function () { return context.manipulationSettings.getNewLineKindAsString(); },
getScriptFileNames: function () { return _this._context.compilerFactory.getSourceFilePaths(); },
getScriptVersion: function (fileName) {
var sourceFile = _this._context.compilerFactory.getSourceFileFromCacheFromFilePath(fileName);
if (sourceFile == null)
return (version++).toString();
return _this._context.compilerFactory.documentRegistry.getSourceFileVersion(sourceFile.compilerNode);
},
getScriptSnapshot: function (fileName) {
if (!fileExistsSync(fileName))
return undefined;
return typescript_1.ts.ScriptSnapshot.fromString(_this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, { markInProject: false }).getFullText());
},
getCurrentDirectory: function () { return context.fileSystemWrapper.getCurrentDirectory(); },
getDefaultLibFileName: function (options) {
if (_this._context.fileSystemWrapper.getFileSystem() instanceof fileSystem_1.DefaultFileSystemHost)
return typescript_1.ts.getDefaultLibFilePath(context.compilerOptions.get());
else
return utils_1.FileUtils.pathJoin(context.fileSystemWrapper.getCurrentDirectory(), "node_modules/typescript/lib/" + typescript_1.ts.getDefaultLibFileName(context.compilerOptions.get()));
},
useCaseSensitiveFileNames: function () { return true; },
readFile: function (path, encoding) {
if (_this._context.compilerFactory.containsSourceFileAtPath(path))
return _this._context.compilerFactory.getSourceFileFromCacheFromFilePath(path).getFullText();
return _this._context.fileSystemWrapper.readFileSync(path, encoding);
},
fileExists: fileExistsSync,
directoryExists: function (dirName) { return _this._context.compilerFactory.containsDirectoryAtPath(dirName) || _this._context.fileSystemWrapper.directoryExistsSync(dirName); }
};
this._compilerHost = {
getSourceFile: function (fileName, languageVersion, onError) {
var sourceFile = _this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, { markInProject: false });
return sourceFile == null ? undefined : sourceFile.compilerNode;
},
// getSourceFileByPath: (...) => {}, // not providing these will force it to use the file name as the file path
// getDefaultLibLocation: (...) => {},
getDefaultLibFileName: function (options) { return languageServiceHost.getDefaultLibFileName(options); },
writeFile: function (filePath, data, writeByteOrderMark, onError, sourceFiles) {
_this._context.fileSystemWrapper.writeFileSync(filePath, data);
},
getCurrentDirectory: function () { return languageServiceHost.getCurrentDirectory(); },
getDirectories: function (path) { return _this._context.fileSystemWrapper.getDirectories(path); },
fileExists: function (fileName) { return languageServiceHost.fileExists(fileName); },
readFile: function (fileName) { return languageServiceHost.readFile(fileName); },
getCanonicalFileName: function (fileName) { return _this._context.fileSystemWrapper.getStandardizedAbsolutePath(fileName); },
useCaseSensitiveFileNames: function () { return languageServiceHost.useCaseSensitiveFileNames(); },
getNewLine: function () { return languageServiceHost.getNewLine(); },
getEnvironmentVariable: function (name) { return process.env[name]; },
directoryExists: function (dirName) { return languageServiceHost.directoryExists(dirName); }
};
this._compilerObject = typescript_1.ts.createLanguageService(languageServiceHost, this._context.compilerFactory.documentRegistry);
this._program = new Program_1.Program(this._context, this._context.compilerFactory.getSourceFilePaths(), this._compilerHost);
this._context.compilerFactory.onSourceFileAdded(function () { return _this._resetProgram(); });
this._context.compilerFactory.onSourceFileRemoved(function () { return _this._resetProgram(); });
}
Object.defineProperty(LanguageService.prototype, "compilerObject", {
/**
* Gets the compiler language service.
*/
get: function () {
return this._compilerObject;
},
enumerable: true,
configurable: true
});
/**
* Resets the program. This should be done whenever any modifications happen.
* @internal
*/
LanguageService.prototype._resetProgram = function () {
this._program._reset(this._context.compilerFactory.getSourceFilePaths(), this._compilerHost);
};
/**
* Gets the language service's program.
*/
LanguageService.prototype.getProgram = function () {
return this._program;
};
/**
* Rename the specified node.
* @param node - Node to rename.
* @param newName - New name for the node.
* @param options - Options for renaming the node.
*/
LanguageService.prototype.renameNode = function (node, newName, options) {
if (options === void 0) { options = {}; }
errors.throwIfWhitespaceOrNotString(newName, "newName");
if (node.getText() === newName)
return;
this.renameLocations(this.findRenameLocations(node, options), newName);
};
/**
* Rename the provided rename locations.
* @param renameLocations - Rename locations.
* @param newName - New name for the node.
*/
LanguageService.prototype.renameLocations = function (renameLocations, newName) {
var e_1, _a, e_2, _b;
var renameLocationsBySourceFile = new utils_1.KeyValueCache();
try {
for (var renameLocations_1 = tslib_1.__values(renameLocations), renameLocations_1_1 = renameLocations_1.next(); !renameLocations_1_1.done; renameLocations_1_1 = renameLocations_1.next()) {
var renameLocation = renameLocations_1_1.value;
var locations = renameLocationsBySourceFile.getOrCreate(renameLocation.getSourceFile(), function () { return []; });
locations.push(renameLocation);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (renameLocations_1_1 && !renameLocations_1_1.done && (_a = renameLocations_1.return)) _a.call(renameLocations_1);
}
finally { if (e_1) throw e_1.error; }
}
try {
for (var _c = tslib_1.__values(renameLocationsBySourceFile.getEntries()), _d = _c.next(); !_d.done; _d = _c.next()) {
var _e = tslib_1.__read(_d.value, 2), sourceFile = _e[0], locations = _e[1];
manipulation_1.replaceSourceFileTextForRename({
sourceFile: sourceFile,
renameLocations: locations,
newName: newName
});
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
}
finally { if (e_2) throw e_2.error; }
}
};
/**
* Gets the definitions for the specified node.
* @param node - Node.
*/
LanguageService.prototype.getDefinitions = function (node) {
return this.getDefinitionsAtPosition(node._sourceFile, node.getStart());
};
/**
* Gets the definitions at the specified position.
* @param sourceFile - Source file.
* @param pos - Position.
*/
LanguageService.prototype.getDefinitionsAtPosition = function (sourceFile, pos) {
var _this = this;
var results = this.compilerObject.getDefinitionAtPosition(sourceFile.getFilePath(), pos) || [];
return results.map(function (info) { return _this._context.compilerFactory.getDefinitionInfo(info); });
};
/**
* Gets the implementations for the specified node.
* @param node - Node.
*/
LanguageService.prototype.getImplementations = function (node) {
return this.getImplementationsAtPosition(node._sourceFile, node.getStart());
};
/**
* Gets the implementations at the specified position.
* @param sourceFile - Source file.
* @param pos - Position.
*/
LanguageService.prototype.getImplementationsAtPosition = function (sourceFile, pos) {
var _this = this;
var results = this.compilerObject.getImplementationAtPosition(sourceFile.getFilePath(), pos) || [];
return results.map(function (location) { return new results_1.ImplementationLocation(_this._context, location); });
};
/**
* Finds references based on the specified node.
* @param node - Node to find references for.
*/
LanguageService.prototype.findReferences = function (node) {
return this.findReferencesAtPosition(node._sourceFile, node.getStart());
};
/**
* Finds the nodes that reference the definition(s) of the specified node.
* @param node - Node.
*/
LanguageService.prototype.findReferencesAsNodes = function (node) {
var referencedSymbols = this.findReferences(node);
return utils_1.ArrayUtils.from(getReferencingNodes());
function getReferencingNodes() {
var e_3, _a, referencedSymbols_1, referencedSymbols_1_1, referencedSymbol, isAlias, references, i, reference, e_3_1;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 7, 8, 9]);
referencedSymbols_1 = tslib_1.__values(referencedSymbols), referencedSymbols_1_1 = referencedSymbols_1.next();
_b.label = 1;
case 1:
if (!!referencedSymbols_1_1.done) return [3 /*break*/, 6];
referencedSymbol = referencedSymbols_1_1.value;
isAlias = referencedSymbol.getDefinition().getKind() === typescript_1.ts.ScriptElementKind.alias;
references = referencedSymbol.getReferences();
i = 0;
_b.label = 2;
case 2:
if (!(i < references.length)) return [3 /*break*/, 5];
reference = references[i];
if (!(isAlias || !reference.isDefinition() || i > 0)) return [3 /*break*/, 4];
return [4 /*yield*/, reference.getNode()];
case 3:
_b.sent();
_b.label = 4;
case 4:
i++;
return [3 /*break*/, 2];
case 5:
referencedSymbols_1_1 = referencedSymbols_1.next();
return [3 /*break*/, 1];
case 6: return [3 /*break*/, 9];
case 7:
e_3_1 = _b.sent();
e_3 = { error: e_3_1 };
return [3 /*break*/, 9];
case 8:
try {
if (referencedSymbols_1_1 && !referencedSymbols_1_1.done && (_a = referencedSymbols_1.return)) _a.call(referencedSymbols_1);
}
finally { if (e_3) throw e_3.error; }
return [7 /*endfinally*/];
case 9: return [2 /*return*/];
}
});
}
};
/**
* Finds references based on the specified position.
* @param sourceFile - Source file.
* @param pos - Position to find the reference at.
*/
LanguageService.prototype.findReferencesAtPosition = function (sourceFile, pos) {
var _this = this;
var results = this.compilerObject.findReferences(sourceFile.getFilePath(), pos) || [];
return results.map(function (s) { return _this._context.compilerFactory.getReferencedSymbol(s); });
};
/**
* Find the rename locations for the specified node.
* @param node - Node to get the rename locations for.
* @param options - Options for renaming.
*/
LanguageService.prototype.findRenameLocations = function (node, options) {
var _this = this;
if (options === void 0) { options = {}; }
var renameLocations = this.compilerObject.findRenameLocations(node._sourceFile.getFilePath(), node.getStart(), options.renameInStrings || false, options.renameInComments || false) || [];
return renameLocations.map(function (l) { return new results_1.RenameLocation(_this._context, l); });
};
/**
* Gets the suggestion diagnostics.
* @param filePathOrSourceFile - The source file or file path to get suggestions for.
*/
LanguageService.prototype.getSuggestionDiagnostics = function (filePathOrSourceFile) {
var _this = this;
var filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
var suggestionDiagnostics = this.compilerObject.getSuggestionDiagnostics(filePath);
return suggestionDiagnostics.map(function (d) { return _this._context.compilerFactory.getDiagnosticWithLocation(d); });
};
/**
* Gets the formatting edits for a range.
* @param filePath - File path.
* @param range - Position range.
* @param formatSettings - Format code settings.
*/
LanguageService.prototype.getFormattingEditsForRange = function (filePath, range, formatSettings) {
return (this.compilerObject.getFormattingEditsForRange(filePath, range[0], range[1], this._getFilledSettings(formatSettings)) || []).map(function (e) { return new results_1.TextChange(e); });
};
/**
* Gets the formatting edits for a document.
* @param filePath - File path of the source file.
* @param formatSettings - Format code settings.
*/
LanguageService.prototype.getFormattingEditsForDocument = function (filePath, formatSettings) {
return (this.compilerObject.getFormattingEditsForDocument(filePath, this._getFilledSettings(formatSettings)) || []).map(function (e) { return new results_1.TextChange(e); });
};
/**
* Gets the formatted text for a document.
* @param filePath - File path of the source file.
* @param formatSettings - Format code settings.
*/
LanguageService.prototype.getFormattedDocumentText = function (filePath, formatSettings) {
var sourceFile = this._context.compilerFactory.getSourceFileFromCacheFromFilePath(filePath);
if (sourceFile == null)
throw new errors.FileNotFoundError(filePath);
formatSettings = this._getFilledSettings(formatSettings);
var formattingEdits = this.getFormattingEditsForDocument(filePath, formatSettings);
var newText = manipulation_1.getTextFromFormattingEdits(sourceFile, formattingEdits);
var newLineChar = formatSettings.newLineCharacter;
if (formatSettings.ensureNewLineAtEndOfFile && !utils_1.StringUtils.endsWith(newText, newLineChar))
newText += newLineChar;
return newText.replace(/\r?\n/g, newLineChar);
};
LanguageService.prototype.getEmitOutput = function (filePathOrSourceFile, emitOnlyDtsFiles) {
var filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
return new results_1.EmitOutput(this._context, filePath, this.compilerObject.getEmitOutput(filePath, emitOnlyDtsFiles));
};
LanguageService.prototype.getIdentationAtPosition = function (filePathOrSourceFile, position, settings) {
var filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
if (settings == null)
settings = this._context.manipulationSettings.getEditorSettings();
else
utils_1.fillDefaultEditorSettings(settings, this._context.manipulationSettings);
return this.compilerObject.getIndentationAtPosition(filePath, position, settings);
};
LanguageService.prototype.organizeImports = function (filePathOrSourceFile, formatSettings, userPreferences) {
var _this = this;
if (formatSettings === void 0) { formatSettings = {}; }
if (userPreferences === void 0) { userPreferences = {}; }
var scope = {
type: "file",
fileName: this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile)
};
return this.compilerObject.organizeImports(scope, this._getFilledSettings(formatSettings), this._getFilledUserPreferences(userPreferences))
.map(function (fileTextChanges) { return new results_1.FileTextChanges(_this._context, fileTextChanges); });
};
/**
* Gets the edit information for applying a refactor at a the provided position in a source file.
* @param filePathOrSourceFile - File path or source file to get the edits for.
* @param formatSettings - Fomat code settings.
* @param positionOrRange - Position in the source file where to apply given refactor.
* @param refactorName - Refactor name.
* @param actionName - Refactor action name.
* @param preferences - User preferences for refactoring.
*/
LanguageService.prototype.getEditsForRefactor = function (filePathOrSourceFile, formatSettings, positionOrRange, refactorName, actionName, preferences) {
if (preferences === void 0) { preferences = {}; }
var filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
var position = typeof positionOrRange === "number" ? positionOrRange : { pos: positionOrRange.getPos(), end: positionOrRange.getEnd() };
var compilerObject = this.compilerObject.getEditsForRefactor(filePath, this._getFilledSettings(formatSettings), position, refactorName, actionName, this._getFilledUserPreferences(preferences));
return compilerObject != null ? new results_1.RefactorEditInfo(this._context, compilerObject) : undefined;
};
/**
* Gets file changes and actions to perform for the provided fixId.
* @param filePathOrSourceFile - File path or source file to get the combined code fixes for.
* @param fixId - Identifier for the code fix (ex. "fixMissingImport"). These ids are found in the `ts.codefix` namespace in the compiler api source.
* @param formatSettings - Format code settings.
* @param preferences - User preferences for refactoring.
*/
LanguageService.prototype.getCombinedCodeFix = function (filePathOrSourceFile, fixId, formatSettings, preferences) {
if (formatSettings === void 0) { formatSettings = {}; }
if (preferences === void 0) { preferences = {}; }
var compilerResult = this.compilerObject.getCombinedCodeFix({
type: "file",
fileName: this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile)
}, fixId, this._getFilledSettings(formatSettings), this._getFilledUserPreferences(preferences || {}));
return new results_1.CombinedCodeActions(this._context, compilerResult);
};
/**
* Gets the edit information for applying a code fix at the provided text range in a source file.
* @param filePathOrSourceFile - File path or source file to get the code fixes for.
* @param start - Start position of the text range to be fixed.
* @param end - End position of the text range to be fixed.
* @param errorCodes - One or more error codes associated with the code fixes to retrieve.
* @param formatOptions - Format code settings.
* @param preferences - User preferences for refactoring.
*/
LanguageService.prototype.getCodeFixesAtPosition = function (filePathOrSourceFile, start, end, errorCodes, formatOptions, preferences) {
var _this = this;
if (formatOptions === void 0) { formatOptions = {}; }
if (preferences === void 0) { preferences = {}; }
var filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
var compilerResult = this.compilerObject.getCodeFixesAtPosition(filePath, start, end, errorCodes, this._getFilledSettings(formatOptions), this._getFilledUserPreferences(preferences || {}));
return compilerResult.map(function (compilerObject) { return new results_1.CodeFixAction(_this._context, compilerObject); });
};
LanguageService.prototype._getFilePathFromFilePathOrSourceFile = function (filePathOrSourceFile) {
var filePath = typeof filePathOrSourceFile === "string"
? this._context.fileSystemWrapper.getStandardizedAbsolutePath(filePathOrSourceFile)
: filePathOrSourceFile.getFilePath();
if (!this._context.compilerFactory.containsSourceFileAtPath(filePath))
throw new errors.FileNotFoundError(filePath);
return filePath;
};
LanguageService.prototype._getFilledSettings = function (settings) {
if (settings["_filled"]) // optimization
return settings;
settings = utils_1.ObjectUtils.assign(this._context.getFormatCodeSettings(), settings);
utils_1.fillDefaultFormatCodeSettings(settings, this._context.manipulationSettings);
settings["_filled"] = true;
return settings;
};
LanguageService.prototype._getFilledUserPreferences = function (userPreferences) {
return utils_1.ObjectUtils.assign(this._context.getUserPreferences(), userPreferences);
};
return LanguageService;
}());
exports.LanguageService = LanguageService;