35 | );
36 | }
37 |
38 | export default App;`;
39 |
--------------------------------------------------------------------------------
/packages/extension/src/parse/module.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 | import { tsquery } from "@phenomnomnominal/tsquery";
3 |
4 | export function findTypeHoleImports(ast: ts.Node) {
5 | return tsquery
6 | .query(ast, "ImportDeclaration > StringLiteral[text='typehole']")
7 | .map((s) => s.parent as ts.ImportDeclaration);
8 | }
9 |
10 | export function resolveImportPath(
11 | projectRoot: string,
12 | moduleName: string,
13 | containingFile: string
14 | ) {
15 | const configFileName = ts.findConfigFile(
16 | projectRoot,
17 | ts.sys.fileExists,
18 | "tsconfig.json"
19 | );
20 |
21 | if (!configFileName) {
22 | return null;
23 | }
24 |
25 | const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
26 |
27 | const compilerOptions = ts.parseJsonConfigFileContent(
28 | configFile.config,
29 | ts.sys,
30 | "./",
31 | undefined,
32 | configFileName
33 | );
34 |
35 | function fileExists(fileName: string): boolean {
36 | return ts.sys.fileExists(fileName);
37 | }
38 |
39 | function readFile(fileName: string): string | undefined {
40 | return ts.sys.readFile(fileName);
41 | }
42 |
43 | const result = ts.resolveModuleName(
44 | moduleName,
45 | containingFile,
46 | compilerOptions.options,
47 | {
48 | fileExists,
49 | readFile,
50 | }
51 | );
52 |
53 | return result.resolvedModule!.resolvedFileName;
54 | }
55 |
56 | export function findLastImport(ast: ts.Node) {
57 | const imports = tsquery.query(ast, "ImportDeclaration");
58 | return imports[imports.length - 1];
59 | }
60 |
61 | export function getNodeEndPosition(node: ts.Node) {
62 | return node.getSourceFile().getLineAndCharacterOfPosition(node.getEnd());
63 | }
64 | export function getNodeStartPosition(node: ts.Node) {
65 | return node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
66 | }
67 |
68 | export function getTypeHoleImport() {
69 | const clause = ts.factory.createImportClause(
70 | false,
71 | ts.factory.createIdentifier("typehole"),
72 | undefined
73 | );
74 | return printAST(
75 | ts.factory.createImportDeclaration(
76 | undefined,
77 | undefined,
78 | clause,
79 | ts.factory.createStringLiteral("typehole")
80 | )
81 | );
82 | }
83 |
84 | export function printAST(ast: ts.Node, sourceFile?: ts.SourceFile) {
85 | const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
86 |
87 | return printer.printNode(
88 | ts.EmitHint.Unspecified,
89 | ast,
90 | sourceFile || ast.getSourceFile()
91 | );
92 | }
93 |
94 | export function findTypeholes(ast: ts.Node | string): ts.CallExpression[] {
95 | const holes = tsquery.query(
96 | ast,
97 | `PropertyAccessExpression > Identifier[name="typehole"]`
98 | );
99 |
100 | return holes
101 | .map((n) => n.parent.parent)
102 | .filter(ts.isCallExpression)
103 | .sort((a, b) => {
104 | const keyA = (a.expression as ts.PropertyAccessExpression).name.getText();
105 | const keyB = (b.expression as ts.PropertyAccessExpression).name.getText();
106 | return keyA.localeCompare(keyB);
107 | });
108 | }
109 |
110 | export function getAST(source: string) {
111 | return tsquery.ast(source, "file.ts", ts.ScriptKind.TSX);
112 | }
113 |
114 | export function getParentOnRootLevel(node: ts.Node): ts.Node {
115 | if (ts.isSourceFile(node.parent)) {
116 | return node;
117 | }
118 | return getParentOnRootLevel(node.parent);
119 | }
120 | export function someParentIs(
121 | node: ts.Node,
122 | test: (node: ts.Node) => boolean
123 | ): boolean {
124 | if (!node.parent) {
125 | return false;
126 | }
127 | if (test(node.parent)) {
128 | return true;
129 | }
130 | return someParentIs(node.parent, test);
131 | }
132 |
133 | export function getParentWithType