Skip to content

Commit

Permalink
feat: Add ClassDeclaration - getExtendsOrThrow() and getBaseClassOrTh…
Browse files Browse the repository at this point in the history
…row.

This is part of an ongoing effort in #74.
  • Loading branch information
dsherret committed Jan 28, 2018
1 parent db34a13 commit 3e24db4
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 5 deletions.
27 changes: 24 additions & 3 deletions code-generation/ensureOrThrowExists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* unexpected happens. They also work nicely with strict null checking.
* --------------------------------------------
*/
import {Type} from "./../src/compiler";
import TsSimpleAst, {Type, ClassDeclaration, InterfaceDeclaration, MethodDeclaration, MethodSignature, Directory} from "./../src/main";
import {InspectorFactory} from "./inspectors";

const inspector = new InspectorFactory().getTsSimpleAstInspector();
Expand All @@ -20,7 +20,7 @@ for (const c of inspector.getPublicClasses()) {
continue;

const orThrowMethod = c.getInstanceMethod(method.getName() + "OrThrow");
if (orThrowMethod == null)
if (orThrowMethod == null && !isIgnoredMethod(c, method))
problems.push(`Expected method ${c.getName()}.${method.getName()} to have a corresponding OrThrow method.`);
}
}
Expand All @@ -31,7 +31,7 @@ for (const i of inspector.getPublicInterfaces()) {
continue;

const orThrowMethod = i.getMethod(method.getName() + "OrThrow");
if (orThrowMethod == null)
if (orThrowMethod == null && !isIgnoredMethod(c, method))
problems.push(`Expected method ${i.getName()}.${method.getName()} to have a corresponding OrThrow method.`);
}
}
Expand All @@ -42,3 +42,24 @@ function doesReturnTypeRequireOrThrow(returnType: Type) {
const unionTypes = returnType.getUnionTypes();
return unionTypes.some(t => t.isUndefinedType());
}

function isIgnoredMethod(parent: ClassDeclaration | InterfaceDeclaration, method: MethodDeclaration | MethodSignature) {
switch (parent.getName()) {
case nameof(TsSimpleAst):
return matches(method.getName(), [
nameof<TsSimpleAst>(a => a.addDirectoryIfExists),
nameof<TsSimpleAst>(a => a.addSourceFileIfExists)
]);
case nameof(Directory):
return matches(method.getName(), [
nameof<Directory>(a => a.addDirectoryIfExists),
nameof<Directory>(a => a.addSourceFileIfExists)
]);
default:
return false;
}
}

function matches(name: string, names: string[]) {
return names.indexOf(name) >= 0;
}
20 changes: 18 additions & 2 deletions src/compiler/class/ClassDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,14 @@ export class ClassDeclaration extends ClassDeclarationBase<ts.ClassDeclaration>
}

/**
* Gets the extends expression.
* Gets the extends expression or throws if it doesn't exist.
*/
getExtendsOrThrow() {
return errors.throwIfNullOrUndefined(this.getExtends(), `Expected to find the extends expression for the class ${this.getName()}.`);
}

/**
* Gets the extends expression or return sundefined if it doesn't exist.
*/
getExtends(): ExpressionWithTypeArguments | undefined {
const extendsClause = this.getHeritageClauseByKind(ts.SyntaxKind.ExtendsKeyword);
Expand Down Expand Up @@ -669,10 +676,19 @@ export class ClassDeclaration extends ClassDeclarationBase<ts.ClassDeclaration>
return this.getType().getBaseTypes();
}

/**
* Gets the base class or throws.
*
* Note: Use getBaseTypes if you need to get the mixins.
*/
getBaseClassOrThrow() {
return errors.throwIfNullOrUndefined(this.getBaseClass(), `Expected to find the base class of ${this.getName()}.`);
}

/**
* Gets the base class.
*
* Note: Use getBaseTypes if the base might be a mixin.
* Note: Use getBaseTypes if you need to get the mixins.
*/
getBaseClass() {
const baseTypes = ArrayUtils.flatten(this.getBaseTypes().map(t => t.isIntersectionType() ? t.getIntersectionTypes() : [t]));
Expand Down
31 changes: 31 additions & 0 deletions src/tests/compiler/class/classDeclarationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ describe(nameof(ClassDeclaration), () => {
});
});

describe(nameof<ClassDeclaration>(d => d.getExtendsOrThrow), () => {
it("should throw when no extends clause exists", () => {
const {firstChild} = getInfoFromText<ClassDeclaration>("class Identifier { }");
expect(() => firstChild.getExtendsOrThrow()).to.throw();
});

it("should return a heritage clause when an extends clause exists", () => {
const {firstChild} = getInfoFromText<ClassDeclaration>("class Identifier extends Base { }");
expect(firstChild.getExtendsOrThrow()).to.be.instanceOf(ExpressionWithTypeArguments);
});
});

describe(nameof<ClassDeclaration>(d => d.setExtends), () => {
function doTest(startCode: string, extendsText: string, expectedCode: string) {
const {firstChild, sourceFile} = getInfoFromText<ClassDeclaration>(startCode);
Expand Down Expand Up @@ -913,6 +925,25 @@ class Child extends Mixin(Base) {}
});
});

describe(nameof<ClassDeclaration>(d => d.getBaseClassOrThrow), () => {
function doTest(text: string, className: string, expectedName: string | undefined) {
const {sourceFile} = getInfoFromText(text);
if (typeof expectedName === "undefined")
expect(() => sourceFile.getClassOrThrow(className).getBaseClassOrThrow()).to.throw();
else {
expect(sourceFile.getClassOrThrow(className).getBaseClassOrThrow().getName()).to.equal(expectedName);
}
}

it("should get the base when it's a class", () => {
doTest("class Base {} class Child extends Base {}", "Child", "Base");
});

it("should throw when there is no base class", () => {
doTest("class Class {}", "Class", undefined);
});
});

describe(nameof<ClassDeclaration>(d => d.getDerivedClasses), () => {
function doTest(text: string, className: string, expectedNames: string[]) {
const {sourceFile} = getInfoFromText(text);
Expand Down

0 comments on commit 3e24db4

Please sign in to comment.