UNPKG

ts-simple-ast

Version:

TypeScript compiler wrapper for AST navigation and code generation.

192 lines (190 loc) 8.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); const manipulation_1 = require("./../../manipulation"); const errors = require("./../../errors"); const utils_1 = require("./../../utils"); const Program_1 = require("./Program"); const results_1 = require("./results"); class LanguageService { /** @internal */ constructor(global) { this.sourceFiles = []; this.global = global; // I don't know what I'm doing for some of this... let version = 0; const fileExistsSync = (path) => this.global.compilerFactory.containsSourceFileAtPath(path) || global.fileSystem.fileExistsSync(path); const languageServiceHost = { getCompilationSettings: () => global.compilerOptions, getNewLine: () => global.manipulationSettings.getNewLineKind(), getScriptFileNames: () => this.sourceFiles.map(s => s.getFilePath()), getScriptVersion: fileName => { return (version++).toString(); }, getScriptSnapshot: fileName => { if (!fileExistsSync(fileName)) return undefined; return ts.ScriptSnapshot.fromString(this.global.compilerFactory.getSourceFileFromFilePath(fileName).getFullText()); }, getCurrentDirectory: () => global.fileSystem.getCurrentDirectory(), getDefaultLibFileName: options => ts.getDefaultLibFilePath(global.compilerOptions), useCaseSensitiveFileNames: () => true, readFile: (path, encoding) => { if (this.global.compilerFactory.containsSourceFileAtPath(path)) return this.global.compilerFactory.getSourceFileFromFilePath(path).getFullText(); return this.global.fileSystem.readFile(path, encoding); }, fileExists: fileExistsSync, directoryExists: dirName => this.global.compilerFactory.containsFileInDirectory(dirName) || this.global.fileSystem.directoryExistsSync(dirName) }; this.compilerHost = { getSourceFile: (fileName, languageVersion, onError) => { return this.global.compilerFactory.getSourceFileFromFilePath(fileName).compilerNode; }, // getSourceFileByPath: (...) => {}, // not providing these will force it to use the file name as the file path // getDefaultLibLocation: (...) => {}, getDefaultLibFileName: (options) => languageServiceHost.getDefaultLibFileName(options), writeFile: (filePath, data, writeByteOrderMark, onError, sourceFiles) => { utils_1.FileUtils.ensureDirectoryExistsSync(this.global.fileSystem, utils_1.FileUtils.getDirPath(filePath)); this.global.fileSystem.writeFileSync(filePath, data); }, getCurrentDirectory: () => languageServiceHost.getCurrentDirectory(), getDirectories: (path) => { console.log("ATTEMPT TO GET DIRECTORIES"); return []; }, fileExists: (fileName) => languageServiceHost.fileExists(fileName), readFile: (fileName) => languageServiceHost.readFile(fileName), getCanonicalFileName: (fileName) => utils_1.FileUtils.getStandardizedAbsolutePath(fileName), useCaseSensitiveFileNames: () => languageServiceHost.useCaseSensitiveFileNames(), getNewLine: () => languageServiceHost.getNewLine(), getEnvironmentVariable: (name) => process.env[name] }; this._compilerObject = ts.createLanguageService(languageServiceHost); } /** * Gets the compiler language service. */ get compilerObject() { return this._compilerObject; } /** * Resets the program. This should be done whenever any modifications happen. * @internal */ resetProgram() { if (this.program != null) this.program.reset(this.getSourceFiles().map(s => s.getFilePath()), this.compilerHost); } /** * Gets the language service's program. */ getProgram() { if (this.program == null) this.program = new Program_1.Program(this.global, this.getSourceFiles().map(s => s.getFilePath()), this.compilerHost); return this.program; } /** * Rename the specified node. * @param node - Node to rename. * @param newName - New name for the node. */ renameNode(node, newName) { errors.throwIfNotStringOrWhitespace(newName, "newName"); if (node.getText() === newName) return; this.renameLocations(this.findRenameLocations(node), newName); } /** * Rename the provided rename locations. * @param renameLocations - Rename locations. * @param newName - New name for the node. */ renameLocations(renameLocations, newName) { const renameLocationsBySourceFile = new utils_1.KeyValueCache(); for (const renameLocation of renameLocations) { const locations = renameLocationsBySourceFile.getOrCreate(renameLocation.getSourceFile(), () => []); locations.push(renameLocation); } for (const [sourceFile, locations] of renameLocationsBySourceFile.getEntries()) { let difference = 0; for (const textSpan of locations.map(l => l.getTextSpan())) { let start = textSpan.getStart(); start -= difference; manipulation_1.replaceNodeText(sourceFile, start, start + textSpan.getLength(), newName); difference += textSpan.getLength() - newName.length; } } } /** * Gets the definitions for the specified node. * @param sourceFile - Source file. * @param node - Node. */ getDefinitions(sourceFile, node) { return this.getDefinitionsAtPosition(sourceFile, node.getStart()); } /** * Gets the definitions at the specified position. * @param sourceFile - Source file. * @param pos - Position. */ getDefinitionsAtPosition(sourceFile, pos) { const results = this.compilerObject.getDefinitionAtPosition(sourceFile.getFilePath(), pos) || []; return results.map(info => new results_1.DefinitionInfo(this.global, info)); } /** * Finds references based on the specified node. * @param sourceFile - Source file. * @param node - Node to find references for. */ findReferences(sourceFile, node) { return this.findReferencesAtPosition(sourceFile, node.getStart()); } /** * Finds references based on the specified position. * @param sourceFile - Source file. * @param pos - Position to find the reference at. */ findReferencesAtPosition(sourceFile, pos) { const results = this.compilerObject.findReferences(sourceFile.getFilePath(), pos) || []; return results.map(s => new results_1.ReferencedSymbol(this.global, s)); } /** * Find the rename locations for the specified node. * @param node - Node to get the rename locations for. */ findRenameLocations(node) { const sourceFile = node.getSourceFile(); const renameLocations = this.compilerObject.findRenameLocations(sourceFile.getFilePath(), node.getStart(), false, false) || []; return renameLocations.map(l => new results_1.RenameLocation(this.global, l)); } /** * @internal */ addSourceFile(sourceFile) { // todo: these source files should be strictly stored in the factory cache this.sourceFiles.push(sourceFile); this.resetProgram(); } /** * @internal */ removeSourceFile(sourceFile) { const index = this.sourceFiles.indexOf(sourceFile); if (index === -1) return false; this.sourceFiles.splice(index, 1); this.resetProgram(); sourceFile.dispose(); // todo: don't dispose, just remove the language service for this node return true; } /** * @internal */ getSourceFiles() { return this.sourceFiles; } } exports.LanguageService = LanguageService; //# sourceMappingURL=LanguageService.js.map