Skip to content

Commit

Permalink
feat: #295 - ClassDeclaration now has a nullable name.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `.getName()` and `.getNameNode()` on ClassDeclaration can now possibly return undefined (ex. `export default class { ... }`).
  • Loading branch information
dsherret committed Mar 28, 2018
1 parent 94b7997 commit 96b9857
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 10 deletions.
12 changes: 12 additions & 0 deletions docs/details/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ const class1 = sourceFile.getClass("Class1");
const firstClassWithConstructor = sourceFile.getClass(c => c.getConstructors().length > 0);
```

### Name

It's important to note that class declarations may not have a name. For example:

```ts
export default class {
// etc...
}
```

For this reason, the methods like `.getName()` and `.getNameNode()` are nullable on `ClassDeclaration`.

### Add/Insert

Add or insert classes to a source file, namespace, or function like declarations by calling `addClass()`, `addClasses()`, `insertClass()`, or `insertClasses()`.
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/base/name/NameableNode.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {ts, SyntaxKind} from "../../../typescript";
import {Constructor} from "../../../Constructor";
import {NameableNodeStructure} from "../../../structures";
import * as errors from "../../../errors";
import {removeChildren, insertIntoParentTextRange} from "../../../manipulation";
import {StringUtils} from "../../../utils";
import {Node, Identifier} from "../../common";
import {callBaseFill} from "../../callBaseFill";

export type NameableNodeExtensionType = Node<ts.Node & { name?: ts.Identifier; }>;

Expand Down Expand Up @@ -77,5 +79,14 @@ export function NameableNode<T extends Constructor<NameableNodeExtensionType>>(B

return this;
}

fill(structure: Partial<NameableNodeStructure>) {
callBaseFill(Base.prototype, this, structure);

if (structure.name != null)
this.rename(structure.name);

return this;
}
};
}
9 changes: 6 additions & 3 deletions src/compiler/class/ClassDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {PropertyDeclarationStructure, MethodDeclarationStructure, ConstructorDec
SetAccessorDeclarationStructure, ClassDeclarationStructure} from "../../structures";
import * as structureToTexts from "../../structureToTexts";
import {Node} from "../common";
import {NamedNode, ExportableNode, ModifierableNode, AmbientableNode, JSDocableNode, TypeParameteredNode, DecoratableNode, HeritageClauseableNode,
import {NameableNode, ExportableNode, ModifierableNode, AmbientableNode, JSDocableNode, TypeParameteredNode, DecoratableNode, HeritageClauseableNode,
ImplementsClauseableNode, TextInsertableNode, ChildOrderableNode} from "../base";
import {HeritageClause} from "../general";
import {AbstractableNode} from "./base";
Expand All @@ -31,7 +31,7 @@ export type ClassStaticMemberTypes = MethodDeclaration | ClassStaticPropertyType
export type ClassMemberTypes = MethodDeclaration | PropertyDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration;

export const ClassDeclarationBase = ChildOrderableNode(TextInsertableNode(ImplementsClauseableNode(HeritageClauseableNode(DecoratableNode(TypeParameteredNode(
NamespaceChildableNode(JSDocableNode(AmbientableNode(AbstractableNode(ExportableNode(ModifierableNode(NamedNode(Statement)))))))
NamespaceChildableNode(JSDocableNode(AmbientableNode(AbstractableNode(ExportableNode(ModifierableNode(NameableNode(Statement)))))))
))))));
export class ClassDeclaration extends ClassDeclarationBase<ts.ClassDeclaration> {
/**
Expand Down Expand Up @@ -927,8 +927,11 @@ export class ClassDeclaration extends ClassDeclarationBase<ts.ClassDeclaration>
}

private getImmediateDerivedClasses() {
const references = this.getNameNode().findReferences();
const classes: ClassDeclaration[] = [];
const nameNode = this.getNameNode();
if (nameNode == null)
return classes;
const references = nameNode.findReferences();
for (const reference of references) {
for (const ref of reference.getReferences()) {
if (ref.isDefinition())
Expand Down
9 changes: 7 additions & 2 deletions src/structureToTexts/class/ClassDeclarationStructureToText.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {ClassDeclarationStructure} from "../../structures";
import {StringUtils} from "../../utils";
import {ClassDeclarationStructure} from "../../structures";
import {StructureToText} from "../StructureToText";
import {ModifierableNodeStructureToText} from "../base";

Expand All @@ -7,6 +8,10 @@ export class ClassDeclarationStructureToText extends StructureToText<ClassDeclar

writeText(structure: ClassDeclarationStructure) {
this.modifierWriter.writeText(structure);
this.writer.write(`class ${structure.name}`).block();
this.writer.write(`class `);
// can be null, ex. `export default class { ... }`
if (!StringUtils.isNullOrWhitespace(structure.name))
this.writer.write(structure.name).write(" ");
this.writer.inlineBlock();
}
}
3 changes: 3 additions & 0 deletions src/structures/base/name/NameableNodeStructure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface NameableNodeStructure {
name?: string;
}
1 change: 1 addition & 0 deletions src/structures/base/name/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./BindingNamedNodeStructure";
export * from "./DeclarationNamedNodeStructure";
export * from "./NamedNodeStructure";
export * from "./NameableNodeStructure";
export * from "./PropertyNameableNodeStructure";
export * from "./PropertyNamedNodeStructure";
9 changes: 7 additions & 2 deletions src/structures/class/ClassDeclarationStructure.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {NamedNodeStructure, ImplementsClauseableNodeStructure, DecoratableNodeStructure, TypeParameteredNodeStructure,
import {NameableNodeStructure, ImplementsClauseableNodeStructure, DecoratableNodeStructure, TypeParameteredNodeStructure,
JSDocableNodeStructure, AmbientableNodeStructure, AbstractableNodeStructure, ExportableNodeStructure} from "../base";
import {PropertyDeclarationStructure} from "./PropertyDeclarationStructure";
import {MethodDeclarationStructure} from "./MethodDeclarationStructure";
Expand All @@ -7,9 +7,14 @@ import {GetAccessorDeclarationStructure} from "./GetAccessorDeclarationStructure
import {SetAccessorDeclarationStructure} from "./SetAccessorDeclarationStructure";

export interface ClassDeclarationStructure
extends NamedNodeStructure, ClassDeclarationSpecificStructure, ImplementsClauseableNodeStructure, DecoratableNodeStructure,
extends NameableNodeStructure, ClassDeclarationSpecificStructure, ImplementsClauseableNodeStructure, DecoratableNodeStructure,
TypeParameteredNodeStructure, JSDocableNodeStructure, AmbientableNodeStructure, AbstractableNodeStructure, ExportableNodeStructure
{
/**
* The class name.
* @remarks Can be undefined. For example: `export default class { ... }`
*/
name?: string;
}

export interface ClassDeclarationSpecificStructure {
Expand Down
4 changes: 4 additions & 0 deletions src/tests/compiler/statement/statementedNode/classTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ describe(nameof(StatementedNode), () => {
}], "class Identifier {\n}\n");
});

it("should insert without a name", () => {
doTest("", 0, [{ isDefaultExport: true }], "export default class {\n}\n");
});

it("should insert at the start of a file", () => {
doTest("enum Enum {\n}\n", 0, [{ name: "Identifier" }], "class Identifier {\n}\n\nenum Enum {\n}\n");
});
Expand Down
4 changes: 2 additions & 2 deletions src/tests/manipulation/getMixinStructureFunctionsTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,11 @@ describe(nameof(getMixinStructureFuncs.fromInitializerExpressionableNode), () =>

describe(nameof(getMixinStructureFuncs.fromNamedNode), () => {
function doTest(startingCode: string, expectedStructure: MakeRequired<structures.NamedNodeStructure>) {
const {firstChild} = getInfoFromText<compiler.ClassDeclaration>(startingCode);
const {firstChild} = getInfoFromText<compiler.InterfaceDeclaration>(startingCode);
expect(getMixinStructureFuncs.fromNamedNode(firstChild)).to.deep.equal(expectedStructure);
}

it("should get the name", () => {
doTest("class Identifier { }", { name: "Identifier" });
doTest("interface Identifier { }", { name: "Identifier" });
});
});
2 changes: 1 addition & 1 deletion src/utils/StringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class StringUtils {
private constructor() {
}

static isNullOrWhitespace(str: string | undefined) {
static isNullOrWhitespace(str: string | undefined): str is undefined {
return typeof str !== "string" || str.trim().length === 0;
}

Expand Down

0 comments on commit 96b9857

Please sign in to comment.