Skip to content

Commit

Permalink
feat: #369 - FunctionDeclaration should have an optional name.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: FunctionDeclaration now has an optional name to support cases where it does (ex. `export default function() {}`)
  • Loading branch information
dsherret committed Jul 18, 2018
1 parent 063e19f commit 176825d
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 16 deletions.
12 changes: 12 additions & 0 deletions docs/details/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ const firstFunctionWithChildFunction = sourceFile.getFunction(f => f.getFunction

Most of the information you can get about functions is covered in other sections.

### Name

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

```ts
export default function() {
// etc...
}
```

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

### Add/Insert

Add or insert enums to a source file, namespace, or function like declarations by calling `addFunction()`, `addFunctions()`, `insertFunction()`, or `insertFunctions()`.
Expand Down
4 changes: 2 additions & 2 deletions lib/ts-simple-ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6246,7 +6246,7 @@ export declare class ArrowFunction extends ArrowFunctionBase<ts.ArrowFunction> {
getEqualsGreaterThan(): Node<ts.Token<SyntaxKind.EqualsGreaterThanToken>>;
}

declare const FunctionDeclarationBase: Constructor<ChildOrderableNode> & Constructor<UnwrappableNode> & Constructor<TextInsertableNode> & Constructor<OverloadableNode> & Constructor<BodyableNode> & Constructor<AsyncableNode> & Constructor<GeneratorableNode> & Constructor<FunctionLikeDeclaration> & Constructor<StatementedNode> & Constructor<AmbientableNode> & Constructor<NamespaceChildableNode> & Constructor<ExportableNode> & Constructor<ModifierableNode> & Constructor<NamedNode> & typeof Node;
declare const FunctionDeclarationBase: Constructor<ChildOrderableNode> & Constructor<UnwrappableNode> & Constructor<TextInsertableNode> & Constructor<OverloadableNode> & Constructor<BodyableNode> & Constructor<AsyncableNode> & Constructor<GeneratorableNode> & Constructor<FunctionLikeDeclaration> & Constructor<StatementedNode> & Constructor<AmbientableNode> & Constructor<NamespaceChildableNode> & Constructor<ExportableNode> & Constructor<ModifierableNode> & Constructor<NameableNode> & typeof Node;

export declare class FunctionDeclaration extends FunctionDeclarationBase<ts.FunctionDeclaration> {
/**
Expand Down Expand Up @@ -9381,7 +9381,7 @@ export interface SourceFileSpecificStructure {
exports?: ExportDeclarationStructure[];
}

export interface FunctionDeclarationStructure extends FunctionDeclarationSpecificStructure, NamedNodeStructure, FunctionLikeDeclarationStructure, StatementedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, AmbientableNodeStructure, ExportableNodeStructure, BodyableNodeStructure {
export interface FunctionDeclarationStructure extends FunctionDeclarationSpecificStructure, NameableNodeStructure, FunctionLikeDeclarationStructure, StatementedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, AmbientableNodeStructure, ExportableNodeStructure, BodyableNodeStructure {
}

export interface FunctionDeclarationSpecificStructure {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/function/FunctionDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { removeOverloadableStatementedNodeChild } from "../../manipulation";
import * as getStructureFuncs from "../../manipulation/helpers/getStructureFunctions";
import { FunctionDeclarationOverloadStructure, FunctionDeclarationStructure } from "../../structures";
import { SyntaxKind, ts } from "../../typescript";
import { AmbientableNode, AsyncableNode, BodyableNode, ChildOrderableNode, ExportableNode, GeneratorableNode, ModifierableNode, NamedNode,
import { AmbientableNode, AsyncableNode, BodyableNode, ChildOrderableNode, ExportableNode, GeneratorableNode, ModifierableNode, NameableNode,
TextInsertableNode, UnwrappableNode } from "../base";
import { callBaseFill } from "../callBaseFill";
import { Node } from "../common";
Expand All @@ -12,7 +12,7 @@ import { FunctionLikeDeclaration } from "./FunctionLikeDeclaration";
import { insertOverloads, OverloadableNode } from "./OverloadableNode";

export const FunctionDeclarationBase = ChildOrderableNode(UnwrappableNode(TextInsertableNode(OverloadableNode(BodyableNode(AsyncableNode(GeneratorableNode(
FunctionLikeDeclaration(StatementedNode(AmbientableNode(NamespaceChildableNode(ExportableNode(ModifierableNode(NamedNode(Node)))))))
FunctionLikeDeclaration(StatementedNode(AmbientableNode(NamespaceChildableNode(ExportableNode(ModifierableNode(NameableNode(Node)))))))
)))))));
export class FunctionDeclaration extends FunctionDeclarationBase<ts.FunctionDeclaration> {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CodeBlockWriter } from "../../codeBlockWriter";
import { FunctionDeclarationOverloadStructure, FunctionDeclarationStructure } from "../../structures";
import { ObjectUtils, setValueIfUndefined } from "../../utils";
import { StringUtils, ObjectUtils, setValueIfUndefined } from "../../utils";
import { FactoryStructurePrinter } from "../FactoryStructurePrinter";

export class FunctionDeclarationStructurePrinter extends FactoryStructurePrinter<FunctionDeclarationStructure> {
Expand Down Expand Up @@ -48,7 +48,7 @@ export class FunctionDeclarationStructurePrinter extends FactoryStructurePrinter
}
}

private printOverloads(writer: CodeBlockWriter, name: string, structures: FunctionDeclarationOverloadStructure[] | undefined) {
private printOverloads(writer: CodeBlockWriter, name: string | undefined, structures: FunctionDeclarationOverloadStructure[] | undefined) {
if (structures == null || structures.length === 0)
return;

Expand All @@ -58,17 +58,18 @@ export class FunctionDeclarationStructurePrinter extends FactoryStructurePrinter
}
}

printOverload(writer: CodeBlockWriter, name: string, structure: FunctionDeclarationOverloadStructure) {
printOverload(writer: CodeBlockWriter, name: string | undefined, structure: FunctionDeclarationOverloadStructure) {
this.printBase(writer, name, structure);
writer.write(";");
}

private printBase(writer: CodeBlockWriter, name: string, structure: FunctionDeclarationOverloadStructure) {
private printBase(writer: CodeBlockWriter, name: string | undefined, structure: FunctionDeclarationOverloadStructure) {
this.factory.forJSDoc().printDocs(writer, structure.docs);
this.factory.forModifierableNode().printText(writer, structure);
writer.write(`function`);
writer.conditionalWrite(structure.isGenerator, "*");
writer.write(` ${name}`);
if (!StringUtils.isNullOrWhitespace(name))
writer.write(` ${name}`);
this.factory.forTypeParameterDeclaration().printTextsWithBrackets(writer, structure.typeParameters);
writer.write("(");
if (structure.parameters != null)
Expand Down
4 changes: 2 additions & 2 deletions src/structures/function/FunctionDeclarationStructure.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { AmbientableNodeStructure, AsyncableNodeStructure, BodyableNodeStructure, ExportableNodeStructure, GeneratorableNodeStructure, JSDocableNodeStructure,
NamedNodeStructure, SignaturedDeclarationStructure, TypeParameteredNodeStructure } from "../base";
NameableNodeStructure, SignaturedDeclarationStructure, TypeParameteredNodeStructure } from "../base";
import { StatementedNodeStructure } from "../statement";
import { FunctionLikeDeclarationStructure } from "./FunctionLikeDeclarationStructure";

export interface FunctionDeclarationStructure
extends FunctionDeclarationSpecificStructure, NamedNodeStructure, FunctionLikeDeclarationStructure, StatementedNodeStructure, AsyncableNodeStructure,
extends FunctionDeclarationSpecificStructure, NameableNodeStructure, FunctionLikeDeclarationStructure, StatementedNodeStructure, AsyncableNodeStructure,
GeneratorableNodeStructure, AmbientableNodeStructure, ExportableNodeStructure, BodyableNodeStructure
{
}
Expand Down
8 changes: 4 additions & 4 deletions src/tests/compiler/common/identifierTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe(nameof(Identifier), () => {
it("should rename", () => {
const text = "function myFunction() {} const reference = myFunction;";
const {firstChild, sourceFile} = getInfoFromText<FunctionDeclaration>(text);
firstChild.getNameNode().rename("newFunction");
firstChild.getNameNodeOrThrow().rename("newFunction");
expect(sourceFile.getFullText()).to.equal(text.replace(/myFunction/g, "newFunction"));
});

Expand All @@ -32,7 +32,7 @@ describe(nameof(Identifier), () => {
expect(definitions[0].getName()).to.equal("myFunction");
expect(definitions[0].getSourceFile().getFullText()).to.equal(sourceFileText);
expect(definitions[0].getKind()).to.equal(ts.ScriptElementKind.functionElement);
expect(definitions[0].getTextSpan().getStart()).to.equal(firstChild.getNameNode().getStart());
expect(definitions[0].getTextSpan().getStart()).to.equal(firstChild.getNameNodeOrThrow().getStart());
expect(definitions[0].getDeclarationNode()).to.equal(firstChild);
});

Expand All @@ -59,7 +59,7 @@ describe(nameof(Identifier), () => {
it("should find all the references", () => {
const {firstChild, sourceFile, project} = getInfoFromText<FunctionDeclaration>("function myFunction() {}\nconst reference = myFunction;");
const secondSourceFile = project.createSourceFile("second.ts", "const reference2 = myFunction;");
const referencedSymbols = firstChild.getNameNode().findReferences();
const referencedSymbols = firstChild.getNameNodeOrThrow().findReferences();
expect(referencedSymbols.length).to.equal(1);
const referencedSymbol = referencedSymbols[0];
const references = referencedSymbol.getReferences();
Expand Down Expand Up @@ -126,7 +126,7 @@ const t = MyNamespace.MyClass;
it("should find all the references and exclude the definition", () => {
const {firstChild, sourceFile, project} = getInfoFromText<FunctionDeclaration>("function myFunction() {}\nconst reference = myFunction;");
const secondSourceFile = project.createSourceFile("second.ts", "const reference2 = myFunction;");
const referencingNodes = firstChild.getNameNode().findReferencesAsNodes();
const referencingNodes = firstChild.getNameNodeOrThrow().findReferencesAsNodes();
expect(referencingNodes.length).to.equal(2);
expect(referencingNodes[0].getParentOrThrow().getText()).to.equal("reference = myFunction");
expect(referencingNodes[1].getParentOrThrow().getText()).to.equal("reference2 = myFunction");
Expand Down
15 changes: 15 additions & 0 deletions src/tests/compiler/function/functionDeclarationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ import { FunctionDeclarationOverloadStructure, FunctionDeclarationSpecificStruct
import { getInfoFromText } from "../testHelpers";

describe(nameof(FunctionDeclaration), () => {
describe(nameof<FunctionDeclaration>(f => f.getName), () => {
function doTest(startCode: string, name: string | undefined) {
const {firstChild} = getInfoFromText<FunctionDeclaration>(startCode);
expect(firstChild.getName()).to.equal(name);
}

it("should be undefined when it doesn't have a name", () => {
doTest("export default function() {}", undefined);
});

it("should get when has a name", () => {
doTest("export default function name() {}", "name");
});
});

describe(nameof<FunctionDeclaration>(f => f.insertOverloads), () => {
function doTest(startCode: string, index: number, structures: FunctionDeclarationOverloadStructure[], expectedCode: string) {
const {firstChild, sourceFile} = getInfoFromText<FunctionDeclaration>(startCode);
Expand Down
5 changes: 5 additions & 0 deletions src/tests/compiler/statement/statementedNode/functionTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ describe(nameof(StatementedNode), () => {
"function Identifier1() {\n}\n\nfunction Identifier2() {\n}\n\nfunction Identifier3() {\n}\n");
});

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

it("should insert ones with a declaration keyword accordingly", () => {
doTest("function Identifier1() {\n}\ndeclare function Identifier4(): string;", 1,
[{ hasDeclareKeyword: true, name: "Identifier2" }, { hasDeclareKeyword: true, name: "Identifier3" }],
Expand Down
2 changes: 1 addition & 1 deletion src/utils/TypeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,7 @@ export class TypeGuards {
static isNameableNode(node: compiler.Node): node is compiler.NameableNode & compiler.Node {
switch (node.getKind()) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
return true;
default:
Expand All @@ -1430,7 +1431,6 @@ export class TypeGuards {
case SyntaxKind.MetaProperty:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.JsxAttribute:
case SyntaxKind.ModuleDeclaration:
Expand Down

0 comments on commit 176825d

Please sign in to comment.