@typespec/emitter-framework 0.9.0-dev.8 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/src/testing/scenario-test/code-block-expectation.d.ts +33 -0
- package/dist/src/testing/scenario-test/code-block-expectation.d.ts.map +1 -0
- package/dist/src/testing/scenario-test/code-block-expectation.js +69 -0
- package/dist/src/testing/scenario-test/code-block-expectation.test.d.ts +2 -0
- package/dist/src/testing/scenario-test/code-block-expectation.test.d.ts.map +1 -0
- package/dist/src/testing/scenario-test/code-block-expectation.test.js +80 -0
- package/dist/src/testing/scenario-test/harness.d.ts +2 -2
- package/dist/src/testing/scenario-test/harness.d.ts.map +1 -1
- package/dist/src/testing/scenario-test/harness.js +69 -158
- package/dist/src/testing/scenario-test/index.d.ts +0 -1
- package/dist/src/testing/scenario-test/index.d.ts.map +1 -1
- package/dist/src/testing/scenario-test/index.js +1 -2
- package/dist/src/testing/scenario-test/snippet-extractor.d.ts +1 -1
- package/dist/src/testing/scenario-test/snippet-extractor.js +1 -1
- package/dist/test/testing/snippet-extractor-csharp.test.js +3 -3
- package/dist/test/testing/snippet-extractor-java.test.js +3 -3
- package/dist/test/testing/snippet-extractor-python.test.js +2 -2
- package/dist/test/testing/snippet-extractor-typescript.test.js +3 -3
- package/package.json +21 -14
- package/src/testing/scenario-test/code-block-expectation.test.ts +95 -0
- package/src/testing/scenario-test/code-block-expectation.ts +115 -0
- package/src/testing/scenario-test/harness.ts +91 -236
- package/src/testing/scenario-test/index.ts +0 -1
- package/src/testing/scenario-test/snippet-extractor.ts +1 -1
- package/test/testing/snippet-extractor-csharp.test.ts +3 -3
- package/test/testing/snippet-extractor-java.test.ts +3 -3
- package/test/testing/snippet-extractor-python.test.ts +2 -2
- package/test/testing/snippet-extractor-typescript.test.ts +3 -3
- package/dist/src/testing/scenario-test/test-host.d.ts +0 -8
- package/dist/src/testing/scenario-test/test-host.d.ts.map +0 -1
- package/dist/src/testing/scenario-test/test-host.js +0 -49
- package/src/testing/scenario-test/test-host.ts +0 -83
|
@@ -1,176 +1,59 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
1
|
+
import { logDiagnostics, NodeHost } from "@typespec/compiler";
|
|
2
|
+
import { expectDiagnosticEmpty, type EmitterTester } from "@typespec/compiler/testing";
|
|
3
3
|
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
4
|
-
import
|
|
5
|
-
import path from "path";
|
|
4
|
+
import { join } from "pathe";
|
|
6
5
|
import { format } from "prettier";
|
|
7
|
-
import { afterAll, describe, expect, it } from "vitest";
|
|
6
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
getExcerptForQuery,
|
|
9
|
+
parseCodeblockExpectation,
|
|
10
|
+
type CodeBlockExpectation,
|
|
11
|
+
} from "./code-block-expectation.js";
|
|
8
12
|
import type { LanguageConfiguration, SnippetExtractor } from "./snippet-extractor.js";
|
|
9
|
-
import { emitWithDiagnostics } from "./test-host.js";
|
|
10
|
-
|
|
11
|
-
const rawArgs = process.env.TEST_ARGS ? process.env.TEST_ARGS.split(" ") : [];
|
|
12
|
-
|
|
13
|
-
// Parse command-line arguments with minimist
|
|
14
|
-
const args = minimist(rawArgs, {
|
|
15
|
-
alias: {
|
|
16
|
-
filter: "f", // Short alias for `--filter`
|
|
17
|
-
},
|
|
18
|
-
default: {
|
|
19
|
-
filter: undefined, // Default to undefined if no filter is provided
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Extract the filter paths from the parsed arguments
|
|
24
|
-
const filterPaths = args.filter
|
|
25
|
-
? Array.isArray(args.filter) // Handle single or multiple file paths
|
|
26
|
-
? args.filter
|
|
27
|
-
: [args.filter]
|
|
28
|
-
: undefined;
|
|
29
13
|
|
|
30
14
|
const SCENARIOS_UPDATE =
|
|
31
15
|
process.env["RECORD"] === "true" || process.env["SCENARIOS_UPDATE"] === "true";
|
|
32
16
|
|
|
33
|
-
type EmitterFunction = (tsp: string, namedArgs: Record<string, string>) => Promise<string>;
|
|
34
|
-
|
|
35
|
-
async function assertGetEmittedFile(
|
|
36
|
-
testLibrary: TypeSpecTestLibrary,
|
|
37
|
-
emitterOutputDir: string,
|
|
38
|
-
file: string,
|
|
39
|
-
code: string,
|
|
40
|
-
) {
|
|
41
|
-
const [emittedFiles, diagnostics] = await emitWithDiagnostics(
|
|
42
|
-
testLibrary,
|
|
43
|
-
emitterOutputDir,
|
|
44
|
-
code,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
48
|
-
const warnings = diagnostics.filter((d) => d.severity === "warning");
|
|
49
|
-
if (warnings.length > 0) {
|
|
50
|
-
// eslint-disable-next-line no-console
|
|
51
|
-
console.warn(`Warning compiling code:\n ${warnings.map((x) => x.message).join("\n")}`);
|
|
52
|
-
}
|
|
53
|
-
if (errors.length > 0) {
|
|
54
|
-
throw new Error(`Error compiling code:\n ${errors.map((x) => x.message).join("\n")}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const normalizedTarget = normalizePath(file);
|
|
58
|
-
const sourceFile = emittedFiles.find((x) => normalizePath(x.path) === normalizedTarget);
|
|
59
|
-
|
|
60
|
-
if (!sourceFile) {
|
|
61
|
-
throw new Error(
|
|
62
|
-
`File ${file} not found in emitted files:\n ${emittedFiles.map((f) => f.path).join("\n")}`,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
return sourceFile;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Mapping of different snapshot types to how to get them.
|
|
70
|
-
* Snapshot types can take single-word string arguments templated in curly braces {} and are otherwise regex
|
|
71
|
-
*/
|
|
72
|
-
function getCodeBlockTypes(
|
|
73
|
-
testLibrary: TypeSpecTestLibrary,
|
|
74
|
-
languageConfiguration: LanguageConfiguration,
|
|
75
|
-
emitterOutputDir: string,
|
|
76
|
-
snippetExtractor: SnippetExtractor,
|
|
77
|
-
): Record<string, EmitterFunction> {
|
|
78
|
-
const languageTags = languageConfiguration.codeBlockTypes.join("|");
|
|
79
|
-
return {
|
|
80
|
-
// Snapshot of a particular interface named {name} in the models file
|
|
81
|
-
[`(${languageTags}) {file} interface {name}`]: async (code, { file, name }) => {
|
|
82
|
-
const sourceFile = await assertGetEmittedFile(testLibrary, emitterOutputDir, file, code);
|
|
83
|
-
const snippet = snippetExtractor.getInterface(sourceFile.content, name);
|
|
84
|
-
|
|
85
|
-
if (!snippet) {
|
|
86
|
-
throw new Error(`Interface ${name} not found in ${file}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return snippet;
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
[`(${languageTags}) {file} type {name}`]: async (code, { file, name }) => {
|
|
93
|
-
const sourceFile = await assertGetEmittedFile(testLibrary, emitterOutputDir, file, code);
|
|
94
|
-
const snippet = snippetExtractor.getTypeAlias(sourceFile.content, name);
|
|
95
|
-
|
|
96
|
-
if (!snippet) {
|
|
97
|
-
throw new Error(`Type alias ${name} not found in ${file}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return snippet;
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
// Snapshot of a particular function named {name} in the models file
|
|
104
|
-
[`(${languageTags}) {file} function {name}`]: async (code, { file, name }) => {
|
|
105
|
-
const sourceFile = await assertGetEmittedFile(testLibrary, emitterOutputDir, file, code);
|
|
106
|
-
const snippet = snippetExtractor.getFunction(sourceFile.content, name);
|
|
107
|
-
|
|
108
|
-
if (!snippet) {
|
|
109
|
-
throw new Error(`Function ${name} not found in ${file}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return snippet;
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
// Snapshot of a particular class named {name} in the models file
|
|
116
|
-
[`(${languageTags}) {file} class {name}`]: async (code, { file, name }) => {
|
|
117
|
-
const sourceFile = await assertGetEmittedFile(testLibrary, emitterOutputDir, file, code);
|
|
118
|
-
const snippet = snippetExtractor.getClass(sourceFile.content, name);
|
|
119
|
-
|
|
120
|
-
if (!snippet) {
|
|
121
|
-
throw new Error(`Class ${name} not found in ${file}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return snippet;
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
// Snapshot of the entire file
|
|
128
|
-
[`(${languageTags}) {file}`]: async (code, { file }) => {
|
|
129
|
-
const sourceFile = await assertGetEmittedFile(testLibrary, emitterOutputDir, file, code);
|
|
130
|
-
return sourceFile.content;
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
17
|
export async function executeScenarios(
|
|
136
|
-
|
|
18
|
+
tester: EmitterTester,
|
|
137
19
|
languageConfiguration: LanguageConfiguration,
|
|
138
20
|
scenariosLocation: string,
|
|
139
|
-
emitterOutputDir: string,
|
|
140
21
|
snippetExtractor: SnippetExtractor,
|
|
141
22
|
) {
|
|
142
|
-
const scenarioList =
|
|
143
|
-
// eslint-disable-next-line no-console
|
|
144
|
-
scenarioList.length && console.log("Filtering scenarios: ", scenarioList);
|
|
23
|
+
const scenarioList = discoverAllScenarios(scenariosLocation);
|
|
145
24
|
|
|
146
|
-
|
|
147
|
-
// Add all scenarios.
|
|
148
|
-
discoverAllScenarios(scenariosLocation, scenarioList);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
describeScenarios(
|
|
152
|
-
scenarioList,
|
|
153
|
-
testLibrary,
|
|
154
|
-
languageConfiguration,
|
|
155
|
-
emitterOutputDir,
|
|
156
|
-
snippetExtractor,
|
|
157
|
-
);
|
|
25
|
+
describeScenarios(scenarioList, tester, languageConfiguration, snippetExtractor);
|
|
158
26
|
}
|
|
159
27
|
|
|
160
|
-
function discoverAllScenarios(
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
28
|
+
function discoverAllScenarios(dir: string): ScenarioFileId[] {
|
|
29
|
+
const scenarios: ScenarioFileId[] = [];
|
|
30
|
+
|
|
31
|
+
function recurse(current: string) {
|
|
32
|
+
const children = readdirSync(join(dir, current));
|
|
33
|
+
for (const child of children) {
|
|
34
|
+
const fullPath = join(dir, current, child);
|
|
35
|
+
const stat = statSync(fullPath);
|
|
36
|
+
if (stat.isDirectory()) {
|
|
37
|
+
recurse(join(current, child));
|
|
38
|
+
} else {
|
|
39
|
+
scenarios.push({ path: fullPath, relativePath: join(current, child) });
|
|
40
|
+
}
|
|
169
41
|
}
|
|
170
42
|
}
|
|
171
43
|
|
|
44
|
+
recurse("");
|
|
172
45
|
return scenarios;
|
|
173
46
|
}
|
|
47
|
+
|
|
48
|
+
interface ScenarioFileId {
|
|
49
|
+
path: string;
|
|
50
|
+
relativePath: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ScenarioFile extends ScenarioFileId {
|
|
54
|
+
scenarios: Scenario[];
|
|
55
|
+
}
|
|
56
|
+
|
|
174
57
|
interface Scenario {
|
|
175
58
|
// The title of the scenario delimited by H1
|
|
176
59
|
title: string;
|
|
@@ -186,54 +69,32 @@ interface ScenarioContents {
|
|
|
186
69
|
|
|
187
70
|
interface SpecCodeBlock {
|
|
188
71
|
kind: "spec" | "test";
|
|
189
|
-
content: string
|
|
72
|
+
content: string;
|
|
190
73
|
}
|
|
191
74
|
|
|
192
75
|
interface TestCodeBlock {
|
|
193
76
|
kind: "test";
|
|
194
77
|
heading: string;
|
|
195
|
-
content: string
|
|
196
|
-
|
|
197
|
-
template: string;
|
|
198
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
199
|
-
fn: Function;
|
|
200
|
-
namedArgs: Record<string, string> | null;
|
|
201
|
-
};
|
|
78
|
+
content: string;
|
|
79
|
+
expectation: CodeBlockExpectation;
|
|
202
80
|
}
|
|
203
81
|
|
|
204
82
|
type ScenarioCodeBlock = SpecCodeBlock | TestCodeBlock;
|
|
205
83
|
|
|
206
|
-
|
|
207
|
-
path: string;
|
|
208
|
-
scenarios: Scenario[];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function parseFile(
|
|
212
|
-
path: string,
|
|
213
|
-
testLibrary: TypeSpecTestLibrary,
|
|
214
|
-
languageConfiguration: LanguageConfiguration,
|
|
215
|
-
emitterOutputDir: string,
|
|
216
|
-
snippetExtractor: SnippetExtractor,
|
|
217
|
-
): ScenarioFile {
|
|
84
|
+
function parseFile(file: ScenarioFileId): ScenarioFile {
|
|
218
85
|
// Read the whole file
|
|
219
|
-
const rawContent = readFileSync(path, { encoding: "utf-8" });
|
|
86
|
+
const rawContent = readFileSync(file.path, { encoding: "utf-8" });
|
|
220
87
|
|
|
221
88
|
// Split the content by H1
|
|
222
89
|
const sections = splitByH1(rawContent);
|
|
223
90
|
|
|
224
91
|
const scenarioFile: ScenarioFile = {
|
|
225
|
-
|
|
92
|
+
...file,
|
|
226
93
|
scenarios: [],
|
|
227
94
|
};
|
|
228
95
|
|
|
229
96
|
for (const section of sections) {
|
|
230
|
-
const scenarioContent = parseScenario(
|
|
231
|
-
section.content,
|
|
232
|
-
testLibrary,
|
|
233
|
-
languageConfiguration,
|
|
234
|
-
emitterOutputDir,
|
|
235
|
-
snippetExtractor,
|
|
236
|
-
);
|
|
97
|
+
const scenarioContent = parseScenario(section.content);
|
|
237
98
|
const scenario: Scenario = {
|
|
238
99
|
title: section.title,
|
|
239
100
|
content: scenarioContent,
|
|
@@ -249,59 +110,43 @@ function isTestCodeBlock(codeBlock: ScenarioCodeBlock): codeBlock is TestCodeBlo
|
|
|
249
110
|
return codeBlock.kind === "test";
|
|
250
111
|
}
|
|
251
112
|
|
|
252
|
-
function parseScenario(
|
|
253
|
-
content: string,
|
|
254
|
-
testLibrary: TypeSpecTestLibrary,
|
|
255
|
-
languageConfiguration: LanguageConfiguration,
|
|
256
|
-
emitterOutputDir: string,
|
|
257
|
-
snippetExtractor: SnippetExtractor,
|
|
258
|
-
): ScenarioContents {
|
|
113
|
+
function parseScenario(content: string): ScenarioContents {
|
|
259
114
|
const rawLines = content.split("\n");
|
|
260
115
|
const scenario: ScenarioContents = {
|
|
261
116
|
lines: [],
|
|
262
|
-
specBlock: { kind: "spec", content:
|
|
117
|
+
specBlock: { kind: "spec", content: "" },
|
|
263
118
|
testBlocks: [],
|
|
264
119
|
};
|
|
265
120
|
|
|
266
|
-
let currentCodeBlock:
|
|
267
|
-
|
|
268
|
-
// Precompute output code block types once
|
|
269
|
-
const outputCodeBlockTypes = getCodeBlockTypes(
|
|
270
|
-
testLibrary,
|
|
271
|
-
languageConfiguration,
|
|
272
|
-
emitterOutputDir,
|
|
273
|
-
snippetExtractor,
|
|
274
|
-
);
|
|
121
|
+
let currentCodeBlock: { heading: string; content: string[] } | null = null;
|
|
275
122
|
|
|
276
123
|
for (const line of rawLines) {
|
|
277
124
|
if (line.startsWith("```") && currentCodeBlock) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
125
|
+
const heading = currentCodeBlock.heading;
|
|
126
|
+
const codeBlockKind =
|
|
127
|
+
heading.includes("tsp") || heading.includes("typespec") ? "spec" : "test";
|
|
128
|
+
const content = currentCodeBlock.content.join("\n");
|
|
129
|
+
if (codeBlockKind === "spec") {
|
|
130
|
+
const codeblock: SpecCodeBlock = {
|
|
131
|
+
kind: "spec",
|
|
132
|
+
content,
|
|
133
|
+
};
|
|
134
|
+
scenario.lines.push(codeblock);
|
|
135
|
+
scenario.specBlock.content = content;
|
|
282
136
|
} else {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
template,
|
|
292
|
-
fn,
|
|
293
|
-
namedArgs: match.groups ?? null,
|
|
294
|
-
};
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
scenario.testBlocks.push(currentCodeBlock);
|
|
137
|
+
const codeblock: TestCodeBlock = {
|
|
138
|
+
kind: "test",
|
|
139
|
+
heading: currentCodeBlock.heading,
|
|
140
|
+
content,
|
|
141
|
+
expectation: parseCodeblockExpectation(currentCodeBlock.heading, content),
|
|
142
|
+
};
|
|
143
|
+
scenario.lines.push(codeblock);
|
|
144
|
+
scenario.testBlocks.push(codeblock);
|
|
299
145
|
}
|
|
300
146
|
currentCodeBlock = null;
|
|
301
147
|
} else if (line.startsWith("```")) {
|
|
302
|
-
const codeBlockKind = line.includes("tsp") || line.includes("typespec") ? "spec" : "test";
|
|
303
148
|
// Start a new code block
|
|
304
|
-
currentCodeBlock = {
|
|
149
|
+
currentCodeBlock = { heading: line.substring(3), content: [] };
|
|
305
150
|
} else if (currentCodeBlock) {
|
|
306
151
|
// Append to code block content
|
|
307
152
|
currentCodeBlock.content.push(line);
|
|
@@ -315,37 +160,47 @@ function parseScenario(
|
|
|
315
160
|
}
|
|
316
161
|
|
|
317
162
|
function describeScenarios(
|
|
318
|
-
scenarioFiles:
|
|
319
|
-
|
|
163
|
+
scenarioFiles: ScenarioFileId[],
|
|
164
|
+
tester: EmitterTester,
|
|
320
165
|
languageConfiguration: LanguageConfiguration,
|
|
321
|
-
emitterOutputDir: string,
|
|
322
166
|
snippetExtractor: SnippetExtractor,
|
|
323
167
|
) {
|
|
324
|
-
const scenarios = scenarioFiles.map((f) =>
|
|
325
|
-
parseFile(f, testLibrary, languageConfiguration, emitterOutputDir, snippetExtractor),
|
|
326
|
-
);
|
|
168
|
+
const scenarios = scenarioFiles.map((f) => parseFile(f));
|
|
327
169
|
|
|
328
170
|
for (const scenarioFile of scenarios) {
|
|
329
|
-
describe(
|
|
171
|
+
describe(`${scenarioFile.relativePath}`, () => {
|
|
330
172
|
for (const scenario of scenarioFile.scenarios) {
|
|
331
173
|
const isOnly = scenario.title.includes("only:");
|
|
332
174
|
const isSkip = scenario.title.includes("skip:");
|
|
333
|
-
|
|
334
175
|
const describeFn = isSkip ? describe.skip : isOnly ? describe.only : describe;
|
|
335
176
|
|
|
177
|
+
let outputFiles: Record<string, string>;
|
|
178
|
+
beforeAll(async () => {
|
|
179
|
+
const code = scenario.content.specBlock.content;
|
|
180
|
+
const [{ outputs }, diagnostics] = await tester.compileAndDiagnose(code);
|
|
181
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
182
|
+
const warnings = diagnostics.filter((d) => d.severity === "warning");
|
|
183
|
+
if (warnings.length > 0) {
|
|
184
|
+
// TODO: this should ideally fail the test or be part of the expectation.
|
|
185
|
+
logDiagnostics(warnings, NodeHost.logSink);
|
|
186
|
+
}
|
|
187
|
+
expectDiagnosticEmpty(errors);
|
|
188
|
+
outputFiles = outputs;
|
|
189
|
+
});
|
|
190
|
+
|
|
336
191
|
describeFn(`Scenario: ${scenario.title}`, () => {
|
|
337
192
|
for (const testBlock of scenario.content.testBlocks) {
|
|
338
193
|
it(`Test: ${testBlock.heading}`, async () => {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
194
|
+
const result = getExcerptForQuery(
|
|
195
|
+
snippetExtractor,
|
|
196
|
+
testBlock.expectation,
|
|
197
|
+
outputFiles,
|
|
343
198
|
);
|
|
344
199
|
|
|
345
200
|
if (SCENARIOS_UPDATE) {
|
|
346
|
-
testBlock.content =
|
|
201
|
+
testBlock.content = await languageConfiguration.format(result);
|
|
347
202
|
} else {
|
|
348
|
-
const expected = await languageConfiguration.format(testBlock.content
|
|
203
|
+
const expected = await languageConfiguration.format(testBlock.content);
|
|
349
204
|
const actual = await languageConfiguration.format(result);
|
|
350
205
|
expect(actual).toBe(expected);
|
|
351
206
|
}
|
|
@@ -375,7 +230,7 @@ async function updateFile(scenarioFile: ScenarioFile) {
|
|
|
375
230
|
} else {
|
|
376
231
|
const heading = isTestCodeBlock(line) ? line.heading : "tsp";
|
|
377
232
|
newContent.push("```" + heading);
|
|
378
|
-
newContent.push(
|
|
233
|
+
newContent.push(line.content);
|
|
379
234
|
newContent.push("```");
|
|
380
235
|
}
|
|
381
236
|
}
|
|
@@ -92,7 +92,7 @@ export interface LanguageConfiguration {
|
|
|
92
92
|
|
|
93
93
|
await Parser.init();
|
|
94
94
|
|
|
95
|
-
export function
|
|
95
|
+
export function createSnippetExtractor(
|
|
96
96
|
languageConfiguration: LanguageConfiguration,
|
|
97
97
|
): SnippetExtractor {
|
|
98
98
|
return new SnippetExtractorImpl(languageConfiguration);
|
|
@@ -2,14 +2,14 @@ import { d } from "@alloy-js/core/testing";
|
|
|
2
2
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
createCSharpExtractorConfig,
|
|
5
|
-
|
|
5
|
+
createSnippetExtractor,
|
|
6
6
|
type SnippetExtractor,
|
|
7
7
|
} from "../../src/testing/index.js";
|
|
8
8
|
describe("C# Snippet Extractor", () => {
|
|
9
9
|
let extractor: SnippetExtractor;
|
|
10
10
|
|
|
11
11
|
beforeEach(async () => {
|
|
12
|
-
extractor =
|
|
12
|
+
extractor = createSnippetExtractor(await createCSharpExtractorConfig());
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
it("should extract a class", () => {
|
|
@@ -66,7 +66,7 @@ describe("C# Snippet Extractor - Enums", () => {
|
|
|
66
66
|
let extractor: SnippetExtractor;
|
|
67
67
|
|
|
68
68
|
beforeEach(async () => {
|
|
69
|
-
extractor =
|
|
69
|
+
extractor = createSnippetExtractor(await createCSharpExtractorConfig());
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it("should extract a basic enum", async () => {
|
|
@@ -2,7 +2,7 @@ import { d } from "@alloy-js/core/testing";
|
|
|
2
2
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
createJavaExtractorConfig,
|
|
5
|
-
|
|
5
|
+
createSnippetExtractor,
|
|
6
6
|
type SnippetExtractor,
|
|
7
7
|
} from "../../src/testing/index.js";
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ describe("Java Snippet Extractor", () => {
|
|
|
10
10
|
let extractor: SnippetExtractor;
|
|
11
11
|
|
|
12
12
|
beforeEach(async () => {
|
|
13
|
-
extractor =
|
|
13
|
+
extractor = createSnippetExtractor(await createJavaExtractorConfig());
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it("should extract a class", () => {
|
|
@@ -67,7 +67,7 @@ describe("Java Snippet Extractor - Enums", () => {
|
|
|
67
67
|
let extractor: SnippetExtractor;
|
|
68
68
|
|
|
69
69
|
beforeEach(async () => {
|
|
70
|
-
extractor =
|
|
70
|
+
extractor = createSnippetExtractor(await createJavaExtractorConfig());
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it("should extract a basic enum", async () => {
|
|
@@ -2,7 +2,7 @@ import { d } from "@alloy-js/core/testing";
|
|
|
2
2
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
createPythonExtractorConfig,
|
|
5
|
-
|
|
5
|
+
createSnippetExtractor,
|
|
6
6
|
type SnippetExtractor,
|
|
7
7
|
} from "../../src/testing/index.js";
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ describe("Python Snippet Extractor", () => {
|
|
|
10
10
|
let extractor: SnippetExtractor;
|
|
11
11
|
|
|
12
12
|
beforeEach(async () => {
|
|
13
|
-
extractor =
|
|
13
|
+
extractor = createSnippetExtractor(await createPythonExtractorConfig());
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it("should extract a class", () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { d } from "@alloy-js/core/testing";
|
|
2
2
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
createSnippetExtractor,
|
|
5
5
|
createTypeScriptExtractorConfig,
|
|
6
6
|
type SnippetExtractor,
|
|
7
7
|
} from "../../src/testing/index.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
describe("TypeScript Snippet Extractor", () => {
|
|
10
10
|
let extractor: SnippetExtractor;
|
|
11
11
|
beforeEach(async () => {
|
|
12
|
-
extractor =
|
|
12
|
+
extractor = createSnippetExtractor(await createTypeScriptExtractorConfig());
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
it("should extract a class", async () => {
|
|
@@ -138,7 +138,7 @@ describe("TypeScript Snippet Extractor", () => {
|
|
|
138
138
|
describe("TypeScript Snippet Extractor - Enums", () => {
|
|
139
139
|
let extractor: SnippetExtractor;
|
|
140
140
|
beforeEach(async () => {
|
|
141
|
-
extractor =
|
|
141
|
+
extractor = createSnippetExtractor(await createTypeScriptExtractorConfig());
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
it("should extract a basic enum", async () => {
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Diagnostic } from "@typespec/compiler";
|
|
2
|
-
import { type TypeSpecTestLibrary } from "@typespec/compiler/testing";
|
|
3
|
-
export interface EmittedFile {
|
|
4
|
-
path: string;
|
|
5
|
-
content: string;
|
|
6
|
-
}
|
|
7
|
-
export declare function emitWithDiagnostics(testLibrary: TypeSpecTestLibrary, emitterOutputDir: string, code: string): Promise<[EmittedFile[], readonly Diagnostic[]]>;
|
|
8
|
-
//# sourceMappingURL=test-host.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-host.d.ts","sourceRoot":"","sources":["../../../../src/testing/scenario-test/test-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAKpC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA4BD,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,mBAAmB,EAChC,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,WAAW,EAAE,EAAE,SAAS,UAAU,EAAE,CAAC,CAAC,CAOjD"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { createTestHost, createTestWrapper } from "@typespec/compiler/testing";
|
|
2
|
-
import { HttpTestLibrary } from "@typespec/http/testing";
|
|
3
|
-
import { RestTestLibrary } from "@typespec/rest/testing";
|
|
4
|
-
import { join, relative } from "path";
|
|
5
|
-
async function createEmitterTestRunner(testLibrary, options = {}) {
|
|
6
|
-
const libraries = options.testHostConfig?.libraries ?? [testLibrary, HttpTestLibrary, RestTestLibrary];
|
|
7
|
-
const host = await createTestHost({
|
|
8
|
-
libraries
|
|
9
|
-
});
|
|
10
|
-
return createTestWrapper(host, {
|
|
11
|
-
autoImports: options.autoImports ?? ["@typespec/http", "@typespec/rest"],
|
|
12
|
-
autoUsings: options.autoUsings ?? ["TypeSpec.Http", "TypeSpec.Rest"],
|
|
13
|
-
compilerOptions: options.compilerOptions ?? {
|
|
14
|
-
noEmit: false,
|
|
15
|
-
emit: [testLibrary.name]
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
export async function emitWithDiagnostics(testLibrary, emitterOutputDir, code) {
|
|
20
|
-
const runner = await createEmitterTestRunner(testLibrary);
|
|
21
|
-
await runner.compileAndDiagnose(code, {
|
|
22
|
-
outputDir: "tsp-output"
|
|
23
|
-
});
|
|
24
|
-
const result = await readFilesRecursively(emitterOutputDir, emitterOutputDir, runner);
|
|
25
|
-
return [result, runner.program.diagnostics];
|
|
26
|
-
}
|
|
27
|
-
async function readFilesRecursively(currentDir, emitterOutputDir, runner) {
|
|
28
|
-
const entries = await runner.program.host.readDir(currentDir);
|
|
29
|
-
const result = [];
|
|
30
|
-
for (const entry of entries) {
|
|
31
|
-
const fullPath = join(currentDir, entry);
|
|
32
|
-
const stat = await runner.program.host.stat(fullPath);
|
|
33
|
-
if (stat.isDirectory()) {
|
|
34
|
-
// Recursively read files in the directory
|
|
35
|
-
const nestedFiles = await readFilesRecursively(fullPath, emitterOutputDir, runner);
|
|
36
|
-
result.push(...nestedFiles);
|
|
37
|
-
} else if (stat.isFile()) {
|
|
38
|
-
// Read the file
|
|
39
|
-
// Read the file and store it with a relative path
|
|
40
|
-
const relativePath = relative(emitterOutputDir, fullPath);
|
|
41
|
-
const fileContent = await runner.program.host.readFile(fullPath);
|
|
42
|
-
result.push({
|
|
43
|
-
path: relativePath,
|
|
44
|
-
content: fileContent.text
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return result;
|
|
49
|
-
}
|