ts-simple-ast
Version:
TypeScript compiler wrapper for AST navigation and code generation.
390 lines (388 loc) • 17 kB
JavaScript
"use strict";
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 common_1 = require("./../common");
const base_1 = require("./../base");
const base_2 = require("./base");
const function_1 = require("./../function");
const namespace_1 = require("./../namespace");
const callBaseFill_1 = require("./../callBaseFill");
const ConstructorDeclaration_1 = require("./ConstructorDeclaration");
const MethodDeclaration_1 = require("./MethodDeclaration");
const PropertyDeclaration_1 = require("./PropertyDeclaration");
const GetAccessorDeclaration_1 = require("./GetAccessorDeclaration");
const SetAccessorDeclaration_1 = require("./SetAccessorDeclaration");
exports.ClassDeclarationBase = base_1.TextInsertableNode(base_1.ImplementsClauseableNode(base_1.HeritageClauseableNode(base_1.DecoratableNode(base_1.TypeParameteredNode(namespace_1.NamespaceChildableNode(base_1.DocumentationableNode(base_1.AmbientableNode(base_2.AbstractableNode(base_1.ExportableNode(base_1.ModifierableNode(base_1.NamedNode(common_1.Node))))))))))));
class ClassDeclaration extends exports.ClassDeclarationBase {
/**
* Fills the node from a structure.
* @param structure - Structure to fill.
*/
fill(structure) {
callBaseFill_1.callBaseFill(exports.ClassDeclarationBase.prototype, this, structure);
if (structure.extends != null)
this.setExtends(structure.extends);
if (structure.ctor != null)
this.addConstructor(structure.ctor);
if (structure.properties != null)
this.addProperties(structure.properties);
if (structure.methods != null)
this.addMethods(structure.methods);
return this;
}
/**
* Sets the extends expression.
* @param text - Text to set as the extends expression.
*/
setExtends(text) {
errors.throwIfNotStringOrWhitespace(text, "text");
const heritageClauses = this.getHeritageClauses();
const extendsClause = heritageClauses.find(c => c.compilerNode.token === ts.SyntaxKind.ExtendsKeyword);
if (extendsClause != null) {
const childSyntaxList = extendsClause.getFirstChildByKindOrThrow(ts.SyntaxKind.SyntaxList);
const childSyntaxListStart = childSyntaxList.getStart();
manipulation_1.insertIntoParent({
parent: extendsClause,
childIndex: childSyntaxList.getChildIndex(),
insertItemsCount: 1,
newText: text,
insertPos: childSyntaxListStart,
replacing: {
length: childSyntaxList.getEnd() - childSyntaxListStart,
nodes: [childSyntaxList]
}
});
return this;
}
const implementsClause = heritageClauses.find(c => c.compilerNode.token === ts.SyntaxKind.ImplementsKeyword);
let insertPos;
if (implementsClause != null)
insertPos = implementsClause.getStart();
else
insertPos = this.getFirstChildByKindOrThrow(ts.SyntaxKind.OpenBraceToken).getStart();
const isLastSpace = /\s/.test(this.getSourceFile().getFullText()[insertPos - 1]);
let newText = `extends ${text} `;
if (!isLastSpace)
newText = " " + newText;
manipulation_1.insertIntoCreatableSyntaxList({
parent: this,
insertPos,
newText,
syntaxList: implementsClause == null ? undefined : implementsClause.getParentSyntaxListOrThrow(),
childIndex: 0,
insertItemsCount: 1
});
return this;
}
/**
* Gets the extends expression.
*/
getExtends() {
const heritageClauses = this.getHeritageClauses();
const extendsClause = heritageClauses.find(c => c.compilerNode.token === ts.SyntaxKind.ExtendsKeyword);
if (extendsClause == null)
return undefined;
const types = extendsClause.getTypes();
return types.length === 0 ? undefined : types[0];
}
/**
* Adds a constructor. Will remove the previous constructor if it exists.
* @param structure - Structure of the constructor.
*/
addConstructor(structure = {}) {
return this.insertConstructor(manipulation_1.getEndIndexFromArray(this.getBodyMembers()), structure);
}
/**
* Inserts a constructor. Will remove the previous constructor if it exists.
* @param index - Index to insert at.
* @param structure - Structure of the constructor.
*/
insertConstructor(index, structure = {}) {
for (const c of this.getConstructors()) {
c.remove();
}
const indentationText = this.getChildIndentationText();
const newLineChar = this.global.manipulationSettings.getNewLineKind();
const code = `${indentationText}constructor() {${newLineChar}${indentationText}}`;
return manipulation_1.insertIntoBracesOrSourceFileWithFillAndGetChildren({
getIndexedChildren: () => this.getBodyMembers(),
sourceFile: this.getSourceFile(),
parent: this,
index,
childCodes: [code],
structures: [structure],
previousBlanklineWhen: () => true,
nextBlanklineWhen: () => true,
expectedKind: ts.SyntaxKind.Constructor,
fillFunction: (node, struct) => node.fill(struct)
})[0];
}
/**
* Gets the constructor declarations.
*/
getConstructors() {
return this.getAllMembers().filter(m => utils_1.TypeGuards.isConstructorDeclaration(m));
}
/**
* Add property.
* @param structure - Structure representing the property.
*/
addProperty(structure) {
return this.addProperties([structure])[0];
}
/**
* Add properties.
* @param structures - Structures representing the properties.
*/
addProperties(structures) {
return this.insertProperties(manipulation_1.getEndIndexFromArray(this.getBodyMembers()), structures);
}
/**
* Insert property.
* @param index - Index to insert at.
* @param structure - Structure representing the property.
*/
insertProperty(index, structure) {
return this.insertProperties(index, [structure])[0];
}
/**
* Insert properties.
* @param index - Index to insert at.
* @param structures - Structures representing the properties.
*/
insertProperties(index, structures) {
const indentationText = this.getChildIndentationText();
// create code
const codes = [];
for (const structure of structures) {
let code = `${indentationText}`;
if (structure.isStatic)
code += "static ";
code += structure.name;
if (structure.hasQuestionToken)
code += "?";
if (structure.type != null && structure.type.length > 0)
code += `: ${structure.type}`;
code += ";";
codes.push(code);
}
return manipulation_1.insertIntoBracesOrSourceFileWithFillAndGetChildren({
getIndexedChildren: () => this.getBodyMembers(),
sourceFile: this.getSourceFile(),
parent: this,
index,
childCodes: codes,
structures,
previousBlanklineWhen: n => utils_1.TypeGuards.isBodyableNode(n) || utils_1.TypeGuards.isBodiedNode(n),
nextBlanklineWhen: n => utils_1.TypeGuards.isBodyableNode(n) || utils_1.TypeGuards.isBodiedNode(n),
expectedKind: ts.SyntaxKind.PropertyDeclaration,
fillFunction: (node, structure) => node.fill(structure)
});
}
getInstanceProperty(nameOrFindFunction) {
return utils_1.getNamedNodeByNameOrFindFunction(this.getInstanceProperties(), nameOrFindFunction);
}
getInstancePropertyOrThrow(nameOrFindFunction) {
return errors.throwIfNullOrUndefined(this.getInstanceProperty(nameOrFindFunction), () => utils_1.getNotFoundErrorMessageForNameOrFindFunction("class instance property", nameOrFindFunction));
}
/**
* Gets the class instance property declarations.
*/
getInstanceProperties() {
return this.getInstanceMembers()
.filter(m => isClassPropertyType(m));
}
getStaticProperty(nameOrFindFunction) {
return utils_1.getNamedNodeByNameOrFindFunction(this.getStaticProperties(), nameOrFindFunction);
}
getStaticPropertyOrThrow(nameOrFindFunction) {
return errors.throwIfNullOrUndefined(this.getStaticProperty(nameOrFindFunction), () => utils_1.getNotFoundErrorMessageForNameOrFindFunction("class static property", nameOrFindFunction));
}
/**
* Gets the class instance property declarations.
*/
getStaticProperties() {
return this.getStaticMembers()
.filter(m => isClassPropertyType(m));
}
/**
* Add method.
* @param structure - Structure representing the method.
*/
addMethod(structure) {
return this.addMethods([structure])[0];
}
/**
* Add methods.
* @param structures - Structures representing the methods.
*/
addMethods(structures) {
return this.insertMethods(manipulation_1.getEndIndexFromArray(this.getBodyMembers()), structures);
}
/**
* Insert method.
* @param index - Index to insert at.
* @param structure - Structure representing the method.
*/
insertMethod(index, structure) {
return this.insertMethods(index, [structure])[0];
}
/**
* Insert methods.
* @param index - Index to insert at.
* @param structures - Structures representing the methods.
*/
insertMethods(index, structures) {
const indentationText = this.getChildIndentationText();
const newLineChar = this.global.manipulationSettings.getNewLineKind();
const isAmbient = this.isAmbient();
// create code
const codes = [];
for (const structure of structures) {
let code = indentationText;
if (structure.isStatic)
code += "static ";
code += `${structure.name}()`;
if (structure.returnType != null && structure.returnType.length > 0)
code += `: ${structure.returnType}`;
if (isAmbient)
code += ";";
else
code += ` {` + newLineChar + indentationText + `}`;
codes.push(code);
}
// insert, fill, and get created nodes
return manipulation_1.insertIntoBracesOrSourceFileWithFillAndGetChildren({
getIndexedChildren: () => this.getBodyMembers(),
sourceFile: this.getSourceFile(),
parent: this,
index,
childCodes: codes,
structures,
previousBlanklineWhen: () => !isAmbient,
nextBlanklineWhen: () => !isAmbient,
separatorNewlineWhen: () => !isAmbient,
expectedKind: ts.SyntaxKind.MethodDeclaration,
fillFunction: (node, structure) => node.fill(structure)
});
}
getInstanceMethod(nameOrFindFunction) {
return utils_1.getNamedNodeByNameOrFindFunction(this.getInstanceMethods(), nameOrFindFunction);
}
getInstanceMethodOrThrow(nameOrFindFunction) {
return errors.throwIfNullOrUndefined(this.getInstanceMethod(nameOrFindFunction), () => utils_1.getNotFoundErrorMessageForNameOrFindFunction("class instance method", nameOrFindFunction));
}
/**
* Gets the class instance method declarations.
*/
getInstanceMethods() {
return this.getInstanceMembers().filter(m => m instanceof MethodDeclaration_1.MethodDeclaration);
}
getStaticMethod(nameOrFindFunction) {
return utils_1.getNamedNodeByNameOrFindFunction(this.getStaticMethods(), nameOrFindFunction);
}
getStaticMethodOrThrow(nameOrFindFunction) {
return errors.throwIfNullOrUndefined(this.getStaticMethod(nameOrFindFunction), () => utils_1.getNotFoundErrorMessageForNameOrFindFunction("class static method", nameOrFindFunction));
}
/**
* Gets the class instance method declarations.
*/
getStaticMethods() {
return this.getStaticMembers().filter(m => m instanceof MethodDeclaration_1.MethodDeclaration);
}
getInstanceMember(nameOrFindFunction) {
return utils_1.getNamedNodeByNameOrFindFunction(this.getInstanceMembers(), nameOrFindFunction);
}
getInstanceMemberOrThrow(nameOrFindFunction) {
return errors.throwIfNullOrUndefined(this.getInstanceMember(nameOrFindFunction), () => utils_1.getNotFoundErrorMessageForNameOrFindFunction("class instance member", nameOrFindFunction));
}
/**
* Gets the instance members.
*/
getInstanceMembers() {
return this.getAllMembers().filter(m => !utils_1.TypeGuards.isConstructorDeclaration(m) && (m instanceof function_1.ParameterDeclaration || !m.isStatic()));
}
getStaticMember(nameOrFindFunction) {
return utils_1.getNamedNodeByNameOrFindFunction(this.getStaticMembers(), nameOrFindFunction);
}
getStaticMemberOrThrow(nameOrFindFunction) {
return errors.throwIfNullOrUndefined(this.getStaticMember(nameOrFindFunction), () => utils_1.getNotFoundErrorMessageForNameOrFindFunction("class static member", nameOrFindFunction));
}
/**
* Gets the static members.
*/
getStaticMembers() {
return this.getAllMembers().filter(m => !utils_1.TypeGuards.isConstructorDeclaration(m) && !(m instanceof function_1.ParameterDeclaration) && m.isStatic());
}
/**
* Gets the constructors, methods, properties, and class parameter properties.
*/
getAllMembers() {
const members = this.getBodyMembers();
const implementationCtors = members.filter(c => utils_1.TypeGuards.isConstructorDeclaration(c) && c.isImplementation());
for (const ctor of implementationCtors) {
// insert after the constructor
let insertIndex = members.indexOf(ctor) + 1;
for (const param of ctor.getParameters()) {
if (param.isParameterProperty()) {
members.splice(insertIndex, 0, param);
insertIndex++;
}
}
}
return members;
}
/**
* Gets all the derived classes.
*/
getDerivedClasses() {
const classes = this.getImmediateDerivedClasses();
for (let i = 0; i < classes.length; i++) {
const derivedClasses = classes[i].getImmediateDerivedClasses();
for (const derivedClass of derivedClasses) {
// don't allow circular references
if (derivedClass !== this && classes.indexOf(derivedClass) === -1)
classes.push(derivedClass);
}
}
return classes;
}
/**
* Removes this class declaration.
*/
remove() {
manipulation_1.removeStatementedNodeChild(this);
}
getImmediateDerivedClasses() {
const references = this.getNameIdentifier().findReferences();
const classes = [];
for (const reference of references) {
for (const ref of reference.getReferences()) {
if (ref.isDefinition())
continue;
const node = ref.getNode();
const nodeParent = node.getParentIfKind(ts.SyntaxKind.ExpressionWithTypeArguments);
if (nodeParent == null)
continue;
const heritageClause = nodeParent.getParentIfKind(ts.SyntaxKind.HeritageClause);
if (heritageClause == null || heritageClause.getToken() !== ts.SyntaxKind.ExtendsKeyword)
continue;
classes.push(heritageClause.getFirstAncestorByKindOrThrow(ts.SyntaxKind.ClassDeclaration));
}
}
return classes;
}
getBodyMembers() {
const members = this.compilerNode.members.map(m => this.global.compilerFactory.getNodeFromCompilerNode(m, this.sourceFile));
// filter out the method declarations or constructor declarations without a body if not ambient
return this.isAmbient() ? members : members.filter(m => !(m instanceof ConstructorDeclaration_1.ConstructorDeclaration || m instanceof MethodDeclaration_1.MethodDeclaration) || m.isImplementation());
}
}
exports.ClassDeclaration = ClassDeclaration;
function isClassPropertyType(m) {
return m instanceof PropertyDeclaration_1.PropertyDeclaration || m instanceof SetAccessorDeclaration_1.SetAccessorDeclaration || m instanceof GetAccessorDeclaration_1.GetAccessorDeclaration || m instanceof function_1.ParameterDeclaration;
}
//# sourceMappingURL=ClassDeclaration.js.map