UNPKG

ts-simple-ast

Version:

TypeScript compiler wrapper for static analysis and code manipulation.

403 lines (402 loc) 23.2 kB
"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;