Skip to content

Commit

Permalink
Use TS Morph to generate type predicates
Browse files Browse the repository at this point in the history
  • Loading branch information
Roaders committed Jul 19, 2024
1 parent d941719 commit 4af95aa
Show file tree
Hide file tree
Showing 5 changed files with 785 additions and 38 deletions.
50 changes: 50 additions & 0 deletions code-generation/generate-type-predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { InterfaceDeclaration, MethodDeclaration, Project, SyntaxKind } from "ts-morph"

// open a new project with just BrowserTypes as the only source file
const project = new Project();
const sourceFile = project.addSourceFileAtPath("./src/api/BrowserTypes.ts")

//get a list of all interfaces in the file
const interfaces = sourceFile.getChildrenOfKind(SyntaxKind.InterfaceDeclaration);

// get a list of all conversion functions in the Convert class
const convert = sourceFile.getClass("Convert");
const convertFunctions = (convert?.getChildrenOfKind(SyntaxKind.MethodDeclaration) ?? []).filter(func => func.getReturnType().getText() === "string");

// generate a list of Interfaces that have an associated conversion function
const matchedInterfaces = convertFunctions.map(func => {
const valueParameter = func.getParameter("value");

const matchingInterface = interfaces.find(interfaceNode => {
return valueParameter?.getType().getText(valueParameter) === interfaceNode.getName();
});


if(matchingInterface != null) {
return {func, matchingInterface};
}

return undefined;
}).filter(((value => value != null) as <T>(value: T | null | undefined) => value is T));

// write a type predicate for each matched interface
matchedInterfaces.forEach(matched => writePredicate(matched.matchingInterface, matched.func));


function writePredicate(matchingInterface: InterfaceDeclaration, func: MethodDeclaration): void{
const predicateName = `is${matchingInterface.getName()}`;

sourceFile.addStatements(`export function ${predicateName}(value: any): value is ${matchingInterface.getName()} {
try{
Convert.${func.getName()}(value);
return true;
} catch(e: any){
return false;
}
}`);

}

sourceFile.formatText();

project.saveSync();
37 changes: 37 additions & 0 deletions code-generation/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
// see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
"files": ["generate-type-predicates.ts"],
"compilerOptions": {
"module": "CommonJS",
"lib": ["dom", "esnext"],
"importHelpers": true,
// output .d.ts declaration files for consumers
"declaration": true,
"outDir": "dist",
// output .js.map sourcemap files for consumers
"sourceMap": true,
// match output dir to input dir. e.g. dist/index instead of dist/src/index
"rootDir": "./",
// stricter type-checking for stronger correctness. Recommended by TS
"strict": true,
// linter checks for common issues
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
"noUnusedLocals": true,
"noUnusedParameters": true,
// use Node's module resolution algorithm, instead of the legacy TS one
"moduleResolution": "node",
// transpile JSX to React.createElement
"jsx": "react",
// interop between ESM and CJS modules. Recommended by TS
"esModuleInterop": true,
// significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
"skipLibCheck": true,
// error out if import and file system have a casing mismatch. Recommended by TS
"forceConsistentCasingInFileNames": true,
// `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
"noEmit": true,
}
}

73 changes: 73 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"prepack": "npm run build",
"typegen": "node s2tQuicktypeUtil.js schemas/context src/context/ContextTypes.ts",
"typegen-browser": "node s2tQuicktypeUtil.js schemas/api/api.schema.json schemas/api/common.schema.json schemas/api/appRequest.schema.json schemas/api/agentResponse.schema.json schemas/api/agentEvent.schema.json schemas/api src/api/BrowserTypes.ts",
"typegen-bridging": "node s2tQuicktypeUtil.js schemas/api schemas/bridging schemas/context/context.schema.json src/bridging/BridgingTypes.ts"
"typegen-bridging": "node s2tQuicktypeUtil.js schemas/api schemas/bridging schemas/context/context.schema.json src/bridging/BridgingTypes.ts",
"posttypegen-browser": "npm run generate-type-predicates",
"generate-type-predicates": "ts-node code-generation/generate-type-predicates.ts"
},
"husky": {
"hooks": {
Expand Down Expand Up @@ -65,6 +67,8 @@
"rimraf": "^5.0.5",
"rollup": "4.12.1",
"ts-jest": "29.1.2",
"ts-morph": "^23.0.0",
"ts-node": "^10.9.2",
"tslib": "^2.0.1",
"typescript": "^4.0.3"
},
Expand Down
Loading

0 comments on commit 4af95aa

Please sign in to comment.