Skip to content

Commit

Permalink
feat: #240 - Wrap JSX nodes.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Feb 12, 2018
1 parent 3e6c862 commit aa4d70a
Show file tree
Hide file tree
Showing 45 changed files with 1,112 additions and 47 deletions.
5 changes: 4 additions & 1 deletion code-generation/createGetStructureFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* -------------------------------------------------
*/
import CodeBlockWriter from "code-block-writer";
import * as fs from "fs";
import * as path from "path";
import {rootFolder} from "./config";
import {Structure} from "./inspectors";

// todo: a lot of this code was written before this library supported manipulation
Expand All @@ -27,7 +30,7 @@ export function createGetStructureFunctions(structures: Structure[]) {
write(writer, structure);
}

return writer.toString();
fs.writeFileSync(path.join(rootFolder, "src/manipulation/helpers/getStructureFunctions.ts"), writer.toString(), { encoding: "utf-8" });
}

// todo: make this better... good enough for now
Expand Down
95 changes: 95 additions & 0 deletions code-generation/createNodePolyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Code generation: Create Node Polyfills
* --------------------------------------
* This creates a file that is being used as a temporary workaround so that this library
* can be used with versions of the compiler < v2.7. In the future, a better solution should be devised.
* --------------------------------------
*/
import * as path from "path";
import * as ts from "typescript";
import {rootFolder} from "./config";
import {InspectorFactory} from "./inspectors";
import {EnumDeclaration, EnumMemberStructure} from "./../src/main";

const nodesToPolyfillNames = ["JsxFragment", "JsxOpeningFragment", "JsxClosingFragment"];
const enumsToPolyfillNames = ["ScriptElementKind"];

export function createNodePolyfills(factory: InspectorFactory) {
const tsInspector = factory.getTsInspector();
const sourceFile = getSourceFile();

sourceFile.addImportDeclaration({
namespaceImport: "ts",
moduleSpecifier: "typescript"
});

fillInterfaces();
fillEnums();

sourceFile.insertStatements(0, writer => {
writer.writeLine("// DO NOT EDIT - This file is automatically generated by createNodePolyfills.ts");
writer.writeLine("// This file is necessary because old supported versions of the compiler won't have these nodes.");
});

sourceFile.save();

function getSourceFile() {
const ast = factory.getAst();
const filePath = path.join(rootFolder, "src/compiler/polyfills.ts");
if (ast.getSourceFile(filePath) != null)
ast.getSourceFileOrThrow(filePath).deleteSync();
return ast.createSourceFile(filePath);
}

function fillInterfaces() {
const nodesToPolyfill = tsInspector.getTsNodes().filter(n => nodesToPolyfillNames.indexOf(n.getName()) >= 0);
for (const node of nodesToPolyfill) {
const nodeInterface = node.getInterface();
const newInterface = sourceFile.addInterface({
name: nodeInterface.getName(),
isExported: true,
extends: nodeInterface.getExtends().map(e => e.getText())
});

for (const nodeProp of nodeInterface.getProperties()) {
// don't include this because old versions of the compiler won't have the syntax kind
if (nodeProp.getName() === "kind")
continue;

newInterface.addProperty({
name: nodeProp.getName(),
type: nodeProp.getTypeNodeOrThrow().getText(),
hasQuestionToken: nodeProp.hasQuestionToken()
});
}
}

for (const newInterface of sourceFile.getInterfaces())
newInterface.rename(newInterface.getName() + "CompilerPolyfill");

// this is sketch, but good enough for now
const prefix = "ts.";
let offset = 0;
for (const cannotFindNameDiagnostic of sourceFile.getDiagnostics().filter(d => d.getCode() === 2304)) {
sourceFile.insertText(cannotFindNameDiagnostic.getStart()! + offset, prefix);
offset += prefix.length;
}
}

function fillEnums() {
const enums = tsInspector.getDeclarationFile().getDescendantsOfKind(ts.SyntaxKind.EnumDeclaration) as EnumDeclaration[];
const enumsToPolyfill = enums.filter(e => enumsToPolyfillNames.indexOf(e.getName()) >= 0);

for (const enumDec of enumsToPolyfill) {
sourceFile.addEnum({
name: enumDec.getName(),
isExported: true,
members: enumDec.getMembers().map(m => ({
name: m.getName(),
docs: m.getJsDocs().map(d => ({ description: d.getInnerText().replace(/\r?\n/g, "\r\n") })),
value: m.getValue()
}) as EnumMemberStructure)
});
}
}
}
14 changes: 10 additions & 4 deletions code-generation/inspectors/InspectorFactory.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import {getAst} from "./../common";
import {TsSimpleAst} from "./../../src/TsSimpleAst";
import {getAst} from "./../common";
import {WrapperFactory} from "./WrapperFactory";
import {TsSimpleAstInspector} from "./TsSimpleAstInspector";
import {TsInspector} from "./TsInspector";

export class InspectorFactory {
private readonly tsSimpleAstInspector: TsSimpleAstInspector;
private readonly tsInspector: TsInspector;
private readonly ast: TsSimpleAst;

constructor() {
const wrapperFactory = new WrapperFactory();
const ast = getAst();

this.tsSimpleAstInspector = new TsSimpleAstInspector(wrapperFactory, ast);
this.tsInspector = new TsInspector(wrapperFactory, ast);
this.ast = getAst();
this.tsSimpleAstInspector = new TsSimpleAstInspector(wrapperFactory, this.ast);
this.tsInspector = new TsInspector(wrapperFactory, this.ast);
}

getTsSimpleAstInspector() {
Expand All @@ -22,4 +24,8 @@ export class InspectorFactory {
getTsInspector() {
return this.tsInspector;
}

getAst() {
return this.ast;
}
}
8 changes: 6 additions & 2 deletions code-generation/inspectors/TsInspector.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import TsSimpleAst, {InterfaceDeclaration} from "./../../src/main";
import TsSimpleAst, {InterfaceDeclaration, SourceFile} from "./../../src/main";
import {Memoize, ArrayUtils} from "./../../src/utils";
import {hasDescendantBaseType} from "./../common";
import {TsNode} from "./ts";
Expand All @@ -8,9 +8,13 @@ export class TsInspector {
constructor(private readonly wrapperFactory: WrapperFactory, private readonly ast: TsSimpleAst) {
}

getDeclarationFile(): SourceFile {
return this.ast.getSourceFileOrThrow("node_modules/typescript/lib/typescript.d.ts");
}

@Memoize
getTsNodes() {
const compilerApiFile = this.ast.getSourceFileOrThrow("node_modules/typescript/lib/typescript.d.ts");
const compilerApiFile = this.getDeclarationFile();
const interfaces: InterfaceDeclaration[] = [];
for (const interfaceDec of ArrayUtils.flatten(compilerApiFile.getNamespaces().map(n => n.getInterfaces()))) {
if (interfaceDec.getBaseTypes().some(t => hasDescendantBaseType(t, checkingType => checkingType.getText() === "ts.Node")))
Expand Down
4 changes: 4 additions & 0 deletions code-generation/inspectors/ts/TsNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export class TsNode {
return this.node.getName();
}

getInterface() {
return this.node;
}

@Memoize
getAssociatedWrappedNode(): WrappedNode | undefined {
const references = this.node.getNameNode().findReferences();
Expand Down
10 changes: 4 additions & 6 deletions code-generation/main.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import * as path from "path";
import * as fs from "fs";
import {rootFolder} from "./config";
import {createGetStructureFunctions} from "./createGetStructureFunctions";
import {createTypeGuardsUtility} from "./createTypeGuardsUtility";
import {createNodePolyfills} from "./createNodePolyfills";
import {InspectorFactory} from "./inspectors";

// setup
const factory = new InspectorFactory();
const inspector = factory.getTsSimpleAstInspector();

// get structure functions
const getStructureCode = createGetStructureFunctions(inspector.getStructures());
fs.writeFileSync(path.join(rootFolder, "src/manipulation/helpers/getStructureFunctions.ts"), getStructureCode, { encoding: "utf-8" });

// create the TypeGuard class
// create
createGetStructureFunctions(inspector.getStructures());
createTypeGuardsUtility(inspector);
createNodePolyfills(factory);
4 changes: 2 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var unusedDefinitionsFilter = filter([
"**", "!*/factories/CompilerFactory.d.ts", "!*/factories/index.d.ts", "!*/tests/**/*.d.ts", "!*/manipulation/**/*.d.ts"
]);

gulp.task("typescript", ["clean-scripts"], function() {
gulp.task("typescript", ["clean-dist"], function() {
var tsProject = ts.createProject("tsconfig.json", {
typescript: require("typescript")
});
Expand Down Expand Up @@ -64,7 +64,7 @@ gulp.task("watch", function() {
gulp.watch("./src/**/*.ts", ["tslint", "typescript"]);
});

gulp.task("clean-scripts", ["clean-code-generation"], function(cb) {
gulp.task("clean-dist", function(cb) {
return del(["./dist/**/*"], cb);
});

Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"typings": "dist/main.d.ts",
"scripts": {
"dopublish": "gulp typescript && gulp code-generate && node dist-cg/code-generation/setSyntaxKindOverloads && node dist-cg/code-generation/flattenDeclarationFiles && npm run code-verification && npm publish",
"install-oldest-ts": "npm install typescript@2.4.2",
"install-latest-ts": "npm install typescript@2.7.1",
"test-ts-2.4.2": "npm install typescript@2.4.2 && npm run test-run",
"test-ts-2.5.3": "npm install typescript@2.5.3 && npm run test-run",
"test-ts-2.6.2": "npm install typescript@2.6.2 && npm run test-run",
Expand All @@ -14,8 +16,9 @@
"test": "npm install typescript@2.7.1 && gulp typescript && npm run test-ts-2.4.2 && npm run test-ts-2.5.3 && npm run test-ts-2.6.2 && npm run test-ts-2.7.1",
"build": "gulp typescript",
"code-generate": "gulp code-generate && node dist-cg/code-generation/main",
"code-verification": "node dist-cg/code-generation/ensureStructuresMatchClasses && node dist-cg/code-generation/ensureNoDefinitionFileErrors && node dist-cg/code-generation/ensureOverloadStructuresMatch",
"output-wrapped-nodes": "gulp code-generate && node dist-cg/code-generation/outputWrappedNodesInfo"
"output-wrapped-nodes": "gulp code-generate && node dist-cg/code-generation/outputWrappedNodesInfo",
"code-verification": "node dist-cg/code-generation/ensureStructuresMatchClasses && npm run ensure-no-definition-file-errors && node dist-cg/code-generation/ensureOverloadStructuresMatch",
"ensure-no-definition-file-errors": "npm run install-oldest-ts && node dist-cg/code-generation/ensureNoDefinitionFileErrors && npm run install-latest-ts && node dist-cg/code-generation/ensureNoDefinitionFileErrors"
},
"repository": {
"type": "git",
Expand Down
41 changes: 40 additions & 1 deletion src/compiler/aliases.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ts from "typescript";
import {Identifier, ComputedPropertyName, QualifiedName} from "./common";
import {PropertyAssignment, ShorthandPropertyAssignment, SpreadAssignment} from "./expression";
import {PropertyAssignment, ShorthandPropertyAssignment, SpreadAssignment, PrimaryExpression, PropertyAccessExpression} from "./expression";
import {JsxAttribute, JsxSpreadAttribute, JsxText, JsxExpression, JsxElement, JsxSelfClosingElement, JsxFragment} from "./jsx";
import {ExternalModuleReference} from "./file";
import {CaseClause, DefaultClause} from "./statement";
import {GetAccessorDeclaration, SetAccessorDeclaration, MethodDeclaration} from "./class";
Expand Down Expand Up @@ -50,6 +51,44 @@ function entityNameValidation() {
}
}

export type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;

/* istanbul ignore next */
function jsxChildValidation() {
const value: ts.JsxChild = null as any;
switch (value.kind) {
case ts.SyntaxKind.JsxText:
case ts.SyntaxKind.JsxExpression:
case ts.SyntaxKind.JsxElement:
case ts.SyntaxKind.JsxSelfClosingElement:
case ts.SyntaxKind.JsxFragment:
return;
default:
const ensureNever: never = value;
}
}

export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;

/* istanbul ignore next */
function jsxAttributeValidation() {
const value: ts.JsxAttributeLike = null as any;
switch (value.kind) {
case ts.SyntaxKind.JsxAttribute:
case ts.SyntaxKind.JsxSpreadAttribute:
return;
default:
const ensureNever: never = value;
}
}

export type JsxTagNameExpression = PrimaryExpression | PropertyAccessExpression;

/* istanbul ignore next */
function jsxTagNameExpressionValidation() {
// todo: some way to validate this
}

export type ObjectLiteralElementLike = PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | MethodDeclaration | AccessorDeclaration;

/* istanbul ignore next */
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/common/Identifier.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as ts from "typescript";
import {Node} from "./Node";
import {Type} from "./../type";
import {ReferencedSymbol, DefinitionInfo, ImplementationLocation} from "./../tools";
import {PrimaryExpression} from "./../expression/PrimaryExpression";

export class Identifier extends Node<ts.Identifier> {
export class Identifier extends PrimaryExpression<ts.Identifier> {
/**
* Gets the text for the identifier.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/expression/Expression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";
import {Type} from "./../type";
import {Node} from "../common";
import {Node} from "./../common/Node";

export class Expression<T extends ts.Expression = ts.Expression> extends Node<T> {
/**
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/expression/LeftHandSideExpression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as ts from "typescript";
import {UpdateExpression} from "./UpdateExpression";

export const LeftHandSideExpressionBase = UpdateExpression;
export class LeftHandSideExpression<T extends ts.LeftHandSideExpression = ts.LeftHandSideExpression> extends LeftHandSideExpressionBase<T> {
export class LeftHandSideExpression<T extends ts.LeftHandSideExpression = ts.LeftHandSideExpression> extends UpdateExpression<T> {
}
3 changes: 1 addition & 2 deletions src/compiler/expression/MemberExpression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as ts from "typescript";
import {LeftHandSideExpression} from "./LeftHandSideExpression";

export const MemberExpressionBase = LeftHandSideExpression;
export class MemberExpression<T extends ts.MemberExpression = ts.MemberExpression> extends MemberExpressionBase<T> {
export class MemberExpression<T extends ts.MemberExpression = ts.MemberExpression> extends LeftHandSideExpression<T> {
}
3 changes: 1 addition & 2 deletions src/compiler/expression/PrimaryExpression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as ts from "typescript";
import {MemberExpression} from "./MemberExpression";

export const PrimaryExpressionBase = MemberExpression;
export class PrimaryExpression<T extends ts.PrimaryExpression = ts.PrimaryExpression> extends PrimaryExpressionBase<T> {
export class PrimaryExpression<T extends ts.PrimaryExpression = ts.PrimaryExpression> extends MemberExpression<T> {
}
3 changes: 1 addition & 2 deletions src/compiler/expression/UnaryExpression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as ts from "typescript";
import {Expression} from "./Expression";

export const UnaryExpressionBase = Expression;
export class UnaryExpression<T extends ts.UnaryExpression = ts.UnaryExpression> extends UnaryExpressionBase<T> {
export class UnaryExpression<T extends ts.UnaryExpression = ts.UnaryExpression> extends Expression<T> {
}
3 changes: 1 addition & 2 deletions src/compiler/expression/UpdateExpression.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as ts from "typescript";
import {UnaryExpression} from "./UnaryExpression";

export const UpdateExpressionBase = UnaryExpression;
export class UpdateExpression<T extends ts.UpdateExpression = ts.UpdateExpression> extends UpdateExpressionBase<T> {
export class UpdateExpression<T extends ts.UpdateExpression = ts.UpdateExpression> extends UnaryExpression<T> {
}
2 changes: 2 additions & 0 deletions src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export * from "./file";
export * from "./function";
export * from "./general";
export * from "./interface";
export * from "./jsx";
export * from "./literal";
export * from "./namespace";
export * from "./polyfills";
export * from "./statement";
export * from "./tools";
export * from "./type";
22 changes: 22 additions & 0 deletions src/compiler/jsx/JsxAttribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as ts from "typescript";
import * as errors from "./../../errors";
import {NamedNode} from "./../base";
import {Node} from "./../common";
import {StringLiteral} from "./../literal";
import {JsxExpression} from "./JsxExpression";

export class JsxAttribute extends NamedNode(Node)<ts.JsxAttribute> {
/**
* Gets the JSX attribute's initializer or throws if it doesn't exist.
*/
getInitializerOrThrow() {
return errors.throwIfNullOrUndefined(this.getInitializer(), `Expected to find an initializer for the JSX attribute '${this.getName()}'`);
}

/**
* Gets the JSX attribute's initializer or returns undefined if it doesn't exist.
*/
getInitializer() {
return this.getNodeFromCompilerNodeIfExists<StringLiteral | JsxExpression>(this.compilerNode.initializer);
}
}

0 comments on commit aa4d70a

Please sign in to comment.