ts-simple-ast
Version:
TypeScript compiler wrapper for AST navigation and code generation.
192 lines (190 loc) • 8.26 kB
JavaScript
"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