ts-simple-ast
Version:
TypeScript compiler wrapper for AST navigation and code generation.
370 lines (368 loc) • 15.2 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const errors = require("./../../errors");
const manipulation_1 = require("./../../manipulation");
const utils_1 = require("./../../utils");
const callBaseFill_1 = require("./../callBaseFill");
const base_1 = require("./../base");
const common_1 = require("./../common");
const statement_1 = require("./../statement");
const ImportDeclaration_1 = require("./ImportDeclaration");
const ExportDeclaration_1 = require("./ExportDeclaration");
// todo: not sure why I need to explicitly type this in order to get VS to not complain... (TS 2.4.1)
exports.SourceFileBase = base_1.TextInsertableNode(statement_1.StatementedNode(common_1.Node));
class SourceFile extends exports.SourceFileBase {
/**
* Initializes a new instance.
* @internal
* @param global - Global container.
* @param node - Underlying node.
*/
constructor(global, node) {
// start hack :(
super(global, node, undefined);
/** @internal */
this._isSaved = false;
this.sourceFile = this;
// end hack
}
/**
* Fills the node from a structure.
* @param structure - Structure to fill.
*/
fill(structure) {
callBaseFill_1.callBaseFill(exports.SourceFileBase.prototype, this, structure);
if (structure.imports != null)
this.addImports(structure.imports);
if (structure.exports != null)
this.addExports(structure.exports);
return this;
}
/**
* @internal
*/
replaceCompilerNode(compilerNode) {
super.replaceCompilerNode(compilerNode);
this.global.resetProgram(); // make sure the program has the latest source file
this._isSaved = false;
}
/**
* Gets the file path.
*/
getFilePath() {
return this.compilerNode.fileName;
}
/**
* Copy this source file to a new file.
* @param filePath - A new file path. Can be relative to the original file or an absolute path.
*/
copy(filePath) {
const absoluteFilePath = utils_1.FileUtils.getAbsoluteOrRelativePathFromPath(filePath, utils_1.FileUtils.getDirPath(this.getFilePath()));
return this.global.compilerFactory.addSourceFileFromText(absoluteFilePath, this.getFullText());
}
/**
* Asynchronously saves this file with any changes.
*/
save() {
return __awaiter(this, void 0, void 0, function* () {
yield utils_1.FileUtils.ensureDirectoryExists(this.global.fileSystem, utils_1.FileUtils.getDirPath(this.getFilePath()));
yield this.global.fileSystem.writeFile(this.getFilePath(), this.getFullText());
this._isSaved = true;
});
}
/**
* Synchronously saves this file with any changes.
*/
saveSync() {
utils_1.FileUtils.ensureDirectoryExistsSync(this.global.fileSystem, utils_1.FileUtils.getDirPath(this.getFilePath()));
this.global.fileSystem.writeFileSync(this.getFilePath(), this.getFullText());
this._isSaved = true;
}
/**
* Gets any referenced files.
*/
getReferencedFiles() {
// todo: add tests
const dirPath = utils_1.FileUtils.getDirPath(this.getFilePath());
return (this.compilerNode.referencedFiles || [])
.map(f => this.global.compilerFactory.getSourceFileFromFilePath(utils_1.FileUtils.pathJoin(dirPath, f.fileName)))
.filter(f => f != null);
}
/**
* Gets the source files for any type reference directives.
*/
getTypeReferenceDirectives() {
// todo: add tests
const dirPath = utils_1.FileUtils.getDirPath(this.getFilePath());
return (this.compilerNode.typeReferenceDirectives || [])
.map(f => this.global.compilerFactory.getSourceFileFromFilePath(utils_1.FileUtils.pathJoin(dirPath, f.fileName)))
.filter(f => f != null);
}
/**
* Gets the source file language variant.
*/
getLanguageVariant() {
return this.compilerNode.languageVariant;
}
/**
* Gets if this is a declaration file.
*/
isDeclarationFile() {
return this.compilerNode.isDeclarationFile;
}
/**
* Gets if this source file has been saved or if the latest changes have been saved.
*/
isSaved() {
return this._isSaved;
}
/**
* Sets if this source file has been saved.
* @internal
*/
setIsSaved(value) {
this._isSaved = value;
}
/**
* Add an import.
* @param structure - Structure that represents the import.
*/
addImport(structure) {
return this.addImports([structure])[0];
}
/**
* Add imports.
* @param structures - Structures that represent the imports.
*/
addImports(structures) {
const imports = this.getImports();
const insertIndex = imports.length === 0 ? 0 : imports[imports.length - 1].getChildIndex() + 1;
return this.insertImports(insertIndex, structures);
}
/**
* Insert an import.
* @param index - Index to insert at.
* @param structure - Structure that represents the import.
*/
insertImport(index, structure) {
return this.insertImports(index, [structure])[0];
}
/**
* Insert imports into a file.
* @param index - Index to insert at.
* @param structures - Structures that represent the imports to insert.
*/
insertImports(index, structures) {
const newLineChar = this.global.manipulationSettings.getNewLineKind();
const indentationText = this.getChildIndentationText();
const texts = structures.map(structure => {
const hasNamedImport = structure.namedImports != null && structure.namedImports.length > 0;
let code = `${indentationText}import`;
// validation
if (hasNamedImport && structure.namespaceImport != null)
throw new errors.InvalidOperationError("An import declaration cannot have both a namespace import and a named import.");
// default import
if (structure.defaultImport != null) {
code += ` ${structure.defaultImport}`;
if (hasNamedImport || structure.namespaceImport != null)
code += ",";
}
// namespace import
if (structure.namespaceImport != null)
code += ` * as ${structure.namespaceImport}`;
// named imports
if (structure.namedImports != null && structure.namedImports.length > 0) {
const namedImportsCode = structure.namedImports.map(n => {
let namedImportCode = n.name;
if (n.alias != null)
namedImportCode += ` as ${n.alias}`;
return namedImportCode;
}).join(", ");
code += ` {${namedImportsCode}}`;
}
// from keyword
if (structure.defaultImport != null || hasNamedImport || structure.namespaceImport != null)
code += " from";
code += ` "${structure.moduleSpecifier}";`;
return code;
});
return this._insertMainChildren(index, texts, structures, ts.SyntaxKind.ImportDeclaration, undefined, {
previousBlanklineWhen: previousMember => !(previousMember instanceof ImportDeclaration_1.ImportDeclaration),
nextBlanklineWhen: nextMember => !(nextMember instanceof ImportDeclaration_1.ImportDeclaration),
separatorNewlineWhen: () => false
});
}
/**
* Gets the first import declaration that matches a condition, or undefined if it doesn't exist.
* @param condition - Condition to get the import by.
*/
getImport(condition) {
return this.getImports().find(condition);
}
/**
* Gets the first import declaration that matches a condition, or throws if it doesn't exist.
* @param condition - Condition to get the import by.
*/
getImportOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getImport(condition), "Expected to find an import with the provided condition.");
}
/**
* Get the file's import declarations.
*/
getImports() {
// todo: remove type assertion
return this.getChildSyntaxListOrThrow().getChildrenOfKind(ts.SyntaxKind.ImportDeclaration);
}
/**
* Add an export.
* @param structure - Structure that represents the export.
*/
addExport(structure) {
return this.addExports([structure])[0];
}
/**
* Add exports.
* @param structures - Structures that represent the exports.
*/
addExports(structures) {
// always insert at end of file because of export {Identifier}; statements
return this.insertExports(this.getChildSyntaxListOrThrow().getChildCount(), structures);
}
/**
* Insert an export.
* @param index - Index to insert at.
* @param structure - Structure that represents the export.
*/
insertExport(index, structure) {
return this.insertExports(index, [structure])[0];
}
/**
* Insert exports into a file.
* @param index - Index to insert at.
* @param structures - Structures that represent the exports to insert.
*/
insertExports(index, structures) {
const newLineChar = this.global.manipulationSettings.getNewLineKind();
const stringChar = this.global.manipulationSettings.getStringChar();
const indentationText = this.getChildIndentationText();
const texts = structures.map(structure => {
const hasModuleSpecifier = structure.moduleSpecifier != null && structure.moduleSpecifier.length > 0;
let code = `${indentationText}export`;
if (structure.namedExports != null && structure.namedExports.length > 0) {
const namedExportsCode = structure.namedExports.map(n => {
let namedExportCode = n.name;
if (n.alias != null)
namedExportCode += ` as ${n.alias}`;
return namedExportCode;
}).join(", ");
code += ` {${namedExportsCode}}`;
}
else if (!hasModuleSpecifier)
code += " {}";
else
code += " *";
if (hasModuleSpecifier)
code += ` from ${stringChar}${structure.moduleSpecifier}${stringChar}`;
code += `;`;
return code;
});
return this._insertMainChildren(index, texts, structures, ts.SyntaxKind.ExportDeclaration, undefined, {
previousBlanklineWhen: previousMember => !(previousMember instanceof ExportDeclaration_1.ExportDeclaration),
nextBlanklineWhen: nextMember => !(nextMember instanceof ExportDeclaration_1.ExportDeclaration),
separatorNewlineWhen: () => false
});
}
/**
* Gets the first export declaration that matches a condition, or undefined if it doesn't exist.
* @param condition - Condition to get the export by.
*/
getExport(condition) {
return this.getExports().find(condition);
}
/**
* Gets the first export declaration that matches a condition, or throws if it doesn't exist.
* @param condition - Condition to get the export by.
*/
getExportOrThrow(condition) {
return errors.throwIfNullOrUndefined(this.getExport(condition), "Expected to find an export with the provided condition.");
}
/**
* Get the file's export declarations.
*/
getExports() {
// todo: remove type assertion
return this.getChildSyntaxListOrThrow().getChildrenOfKind(ts.SyntaxKind.ExportDeclaration);
}
/**
* Gets the default export symbol of the file.
*/
getDefaultExportSymbol() {
const sourceFileSymbol = this.getSymbol();
// will be undefined when the source file doesn't have an export
if (sourceFileSymbol == null)
return undefined;
return sourceFileSymbol.getExportByName("default");
}
/**
* Gets the default export symbol of the file or throws if it doesn't exist.
*/
getDefaultExportSymbolOrThrow() {
return errors.throwIfNullOrUndefined(this.getDefaultExportSymbol(), "Expected to find a default export symbol");
}
/**
* Gets the compiler diagnostics.
*/
getDiagnostics() {
// todo: implement cancellation token
const compilerDiagnostics = ts.getPreEmitDiagnostics(this.global.program.compilerObject, this.compilerNode);
return compilerDiagnostics.map(d => this.global.compilerFactory.getDiagnostic(d));
}
/**
* Removes any "export default";
*/
removeDefaultExport(defaultExportSymbol) {
defaultExportSymbol = defaultExportSymbol || this.getDefaultExportSymbol();
if (defaultExportSymbol == null)
return this;
const declaration = defaultExportSymbol.getDeclarations()[0];
if (declaration.compilerNode.kind === ts.SyntaxKind.ExportAssignment)
manipulation_1.removeChildrenWithFormatting({ children: [declaration], getSiblingFormatting: () => manipulation_1.FormattingKind.Newline });
else if (utils_1.TypeGuards.isModifierableNode(declaration)) {
declaration.toggleModifier("default", false);
declaration.toggleModifier("export", false);
}
return this;
}
/**
* Emits the source file.
*/
emit(options) {
return this.global.program.emit(Object.assign({ targetSourceFile: this }, options));
}
/**
* Formats the source file text using the internal typescript printer.
*
* WARNING: This will dispose any previously navigated descendant nodes.
*/
formatText(opts = {}) {
const printer = ts.createPrinter({
newLine: utils_1.newLineKindToTs(this.global.manipulationSettings.getNewLineKind()),
removeComments: opts.removeComments || false
});
const newText = printer.printFile(this.compilerNode);
const replacementSourceFile = this.global.compilerFactory.createTempSourceFileFromText(newText, this.getFilePath());
this.getChildren().forEach(d => d.dispose()); // this will dispose all the descendants
this.replaceCompilerNode(replacementSourceFile.compilerNode);
}
}
exports.SourceFile = SourceFile;
//# sourceMappingURL=SourceFile.js.map