UNPKG

ts-simple-ast

Version:

TypeScript compiler wrapper for AST navigation and code generation.

139 lines (137 loc) 6.58 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 fileExists = (path) => this.global.compilerFactory.containsSourceFileAtPath(path) || global.fileSystem.fileExists(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 (!fileExists(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, directoryExists: dirName => this.global.compilerFactory.containsFileInDirectory(dirName) || this.global.fileSystem.directoryExists(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: (fileName, data, writeByteOrderMark, onError, sourceFiles) => { this.global.fileSystem.writeFileSync(fileName, 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; } renameNode(node, newName) { errors.throwIfNotStringOrWhitespace(newName, "newName"); if (node.getText() === newName) return; this.renameLocations(this.findRenameLocations(node), newName); } 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; } } } findReferences(sourceFile, posOrNode) { const pos = typeof posOrNode === "number" ? posOrNode : posOrNode.getStart(); const results = this.compilerObject.findReferences(sourceFile.getFilePath(), pos) || []; return results.map(s => new results_1.ReferencedSymbol(this.global, s)); } 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)); } addSourceFile(sourceFile) { // todo: these source files should be strictly stored in the factory cache this.sourceFiles.push(sourceFile); this.resetProgram(); } 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; } getSourceFiles() { return this.sourceFiles; } } exports.LanguageService = LanguageService; //# sourceMappingURL=LanguageService.js.map