VSCodium / VS Code extension which lets you run OCaml PPX Expect and PPX Inline Test with the (native) Test Explorer UI.
at main 218 lines 6.5 kB view raw
1/* 2 * SPDX-License-Identifier: MIT 3 * Copyright (C) 2023 Roland Csaszar 4 * 5 * Project: vscode-ocaml-expect-inline 6 * File: parse_source.ts 7 * Date: 04.Mar.2023 8 * 9 * ============================================================================== 10 * Parse a single source file for test definitions. 11 */ 12 13import * as c from "./constants"; 14import * as h from "./extension_helpers"; 15import * as io from "./osInteraction"; 16import * as p from "./parsing"; 17import * as t from "./list_tests"; 18import * as vscode from "vscode"; 19 20/** 21 * Parse the given source file for tests and add them to the Test Explorer's 22 * tree. 23 * @param env The extension's environment. 24 * @param source The source file to parse for tests. 25 */ 26// eslint-disable-next-line max-lines-per-function, max-statements 27export async function parseTextDocument( 28 env: h.Env, 29 source: vscode.TextDocument 30) { 31 const relPath = h.toRelativePath(source.uri) as { 32 root: vscode.WorkspaceFolder; 33 path: string; 34 }; 35 const sanitizedTests = p 36 .parseTextForTests(source.getText()) 37 .map(({ name, range }) => ({ 38 name: 39 name === "_" 40 ? h.toTestName(relPath.path, range.start.line + 1) 41 : name, 42 range, 43 })); 44 45 const parents = io.getListParentDirs(relPath.path); 46 for (const parent of parents) { 47 if ( 48 // eslint-disable-next-line no-await-in-loop 49 await hasAddedTests(env, { 50 relPath, 51 parent, 52 sanitizedTests, 53 source, 54 }) 55 ) { 56 return; 57 } 58 } 59} 60 61/** 62 * Return `true` if we have found a dune file containing a library definition 63 * and the tests have been added to the Test Explorer's tree. 64 * @param env The extension's environment. 65 * @param data The needed data. 66 * @returns `true` if we have found a dune file containing a library definition. 67 * `false` else. 68 */ 69async function hasAddedTests( 70 env: h.Env, 71 data: { 72 relPath: { root: vscode.WorkspaceFolder; path: string }; 73 parent: string; 74 sanitizedTests: { 75 name: string; 76 range: vscode.Range; 77 }[]; 78 source: vscode.TextDocument; 79 } 80) { 81 const duneFile = vscode.Uri.joinPath( 82 data.relPath.root.uri, 83 data.parent.concat("/" + c.duneFileName) 84 ); 85 if (await io.existsIsFile(duneFile)) { 86 const bytes = await vscode.workspace.fs.readFile(duneFile); 87 const libName = p.parseDuneLib(bytes.toString()); 88 if (libName) { 89 env.outChannel.appendLine( 90 `Found library "${libName}" in dune file ${duneFile.path}` 91 ); 92 await addTests(env, { 93 relPath: data.relPath, 94 parent: data.parent, 95 sanitizedTests: data.sanitizedTests, 96 source: data.source, 97 libName, 98 }); 99 return true; 100 } 101 } 102 return false; 103} 104 105/** 106 * Add all tests to the Test Explorer's tree. 107 * @param env The extension's environment. 108 * @param data The needed data. 109 */ 110async function addTests( 111 env: h.Env, 112 data: { 113 relPath: { root: vscode.WorkspaceFolder; path: string }; 114 parent: string; 115 sanitizedTests: { 116 name: string; 117 range: vscode.Range; 118 }[]; 119 source: vscode.TextDocument; 120 libName: string; 121 } 122) { 123 const { groupItem } = getOrCreateParents(env, data.relPath); 124 // eslint-disable-next-line no-await-in-loop 125 const out = await io.runDuneBuild(undefined, data.relPath.root, { 126 duneCmd: c.getCfgDunePath(env.config), 127 libDir: data.parent, 128 libName: data.libName, 129 }); 130 env.outChannel.appendLine( 131 `Dune build output stdout: ${out.stdout} stderr: ${out.stderr} error: ${ 132 out.error ? out.error : "" 133 }` 134 ); 135 136 data.sanitizedTests.forEach((test) => 137 t.addTestItem(env, { 138 t: { 139 name: test.name, 140 line: test.range.start.line + 1, 141 startCol: test.range.start.character, 142 endCol: test.range.end.character, 143 }, 144 root: data.relPath.root, 145 sourcePath: data.source.uri, 146 groupItem, 147 runnerPath: c.fullRunnerPath(data.parent, data.libName), 148 }) 149 ); 150} 151 152/** 153 * Return the two top nodes of the Test Explorer tree: 154 * `{ workspaceItem, groupItem }`. 155 * If they do not exist, create them. 156 * @param env The extension's environment. 157 * @param relPath The relative path to the source file. 158 * @returns The two top nodes of the Test Explorer tree: 159 * `{ workspaceItem, groupItem }`. 160 */ 161function getOrCreateParents( 162 env: h.Env, 163 relPath: { root: vscode.WorkspaceFolder; path: string } 164) { 165 const workspaceItem = getOrCreateItem(env, { 166 items: env.controller.items, 167 id: relPath.root.name, 168 label: c.workspaceLabel(relPath.root.name), 169 uri: relPath.root.uri, 170 delete: false, 171 }); 172 const groupItem = getOrCreateItem(env, { 173 items: workspaceItem.children, 174 id: relPath.path, 175 label: relPath.path, 176 uri: vscode.Uri.joinPath(relPath.root.uri, relPath.path), 177 delete: true, 178 }); 179 return { workspaceItem, groupItem }; 180} 181 182/** 183 * Either return an existing `TestItem` with the given data or create one and 184 * add it to the Test Explorer. 185 * If `data.delete` is set, an existing `TestItem` and all of its children are 186 * being deleted and created. 187 * @param env The extension's environment. 188 * @param data The needed data. 189 * @returns The created or existing `TestItem`. 190 */ 191function getOrCreateItem( 192 env: h.Env, 193 data: { 194 items: vscode.TestItemCollection; 195 id: string; 196 label: string; 197 uri: vscode.Uri; 198 delete: boolean; 199 } 200) { 201 let testItem = data.items.get(data.id); 202 if (!testItem) { 203 testItem = env.controller.createTestItem(data.id, data.label, data.uri); 204 data.items.add(testItem); 205 } else if (data.delete) { 206 testItem.children.forEach((i) => { 207 env.outChannel.appendLine( 208 `Deleting "${i.label}" Line: ${parseInt(i.id, 10) + 1}` 209 ); 210 testItem?.children.delete(i.id); 211 }); 212 data.items.delete(data.id); 213 testItem = env.controller.createTestItem(data.id, data.label, data.uri); 214 data.items.add(testItem); 215 } 216 217 return testItem; 218}