Skip to content

Commit 897aecc

Browse files
authored
[appservice] Update appservice projects to use snippets (#32502)
### Packages impacted by this PR - @azure-rest/arm-appservice - @azure/arm-appservice - @azure/arm-appservice-profile-2020-09-01-hybrid ### Issues associated with this PR - #32416 ### Describe the problem that is addressed by this PR Updates all projects under `appservice` to use snippets extraction. ### What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen? ### Are there test cases added in this PR? _(If not, why?)_ ### Provide a list of related PRs _(if any)_ ### Command used to generate this PR:**_(Applicable only to SDK release request PRs)_ ### Checklists - [ ] Added impacted package name to the issue description - [ ] Does this PR needs any fixes in the SDK Generator?** _(If so, create an Issue in the [Autorest/typescript](https://github.com/Azure/autorest.typescript) repository and link it here)_ - [ ] Added a changelog (if necessary)
1 parent 1a03c44 commit 897aecc

File tree

21 files changed

+408
-229
lines changed

21 files changed

+408
-229
lines changed

common/tools/dev-tool/src/commands/run/update-snippets.ts

+82-20
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,12 @@ async function parseSnippetDefinitions(
229229
sourceFile,
230230
);
231231

232-
const imports: [string, string][] = [];
232+
const imports: { name: string; moduleSpecifier: string; isDefault: boolean }[] = [];
233233

234234
// This nested visitor is just for extracting the imports of a symbol.
235235
const symbolImportVisitor: ts.Visitor = (node: ts.Node) => {
236-
let importLocations: string[] | undefined;
237-
if (
238-
ts.isIdentifier(node) &&
239-
(importLocations = extractImportLocations(node)) !== undefined
240-
) {
236+
if (ts.isIdentifier(node)) {
237+
const importLocations = extractImportLocations(node);
241238
if (importLocations.length > 1) {
242239
// We can probably handle this, but it's an obscure case and it's probably better to let it error out and
243240
// then observe whether or not we actually need (or even _want_) snippets with merged imports.
@@ -247,7 +244,10 @@ async function parseSnippetDefinitions(
247244
} else if (importLocations.length === 1) {
248245
// The symbol was imported, so we need to track the imports to add them to the snippet later.
249246
log.debug(`symbol ${node.text} was imported from ${importLocations[0]}`);
250-
imports.push([node.text, importLocations[0]]);
247+
imports.push({
248+
name: node.text,
249+
...importLocations[0],
250+
});
251251
}
252252
// else the symbol was not imported within this file, so it must be defined in the ambient context of the
253253
// module, so we don't need to generate any code for it.
@@ -264,23 +264,56 @@ async function parseSnippetDefinitions(
264264
// file using `convert`.
265265
log.debug(`found a snippet named ${name.text}: \n${contents}`);
266266

267+
interface ImportedSymbols {
268+
default?: string;
269+
named?: Set<string>;
270+
}
271+
267272
// We have a loose map of imports in the form { [k:symbol]: module } and we need to anneal it into a map
268273
// { [k: module]: symbol[] } (one import statement per module with the whole list of symbols imported from it)
269-
const importMap = new Map<string, Set<string>>(
270-
imports.map(([, module]) => [module, new Set()]),
271-
);
274+
const importMap = new Map<string, ImportedSymbols>();
272275

273-
for (const [symbol, name] of imports) {
274-
importMap.get(name)!.add(symbol);
276+
for (const { name, moduleSpecifier, isDefault } of imports) {
277+
let moduleImports = importMap.get(moduleSpecifier);
278+
if (!moduleImports) {
279+
moduleImports = {};
280+
importMap.set(moduleSpecifier, moduleImports);
281+
}
282+
if (isDefault) {
283+
if (moduleImports.default) {
284+
throw new Error(
285+
`unrecoverable error: multiple default imports from the same module '${moduleSpecifier}'`,
286+
);
287+
}
288+
moduleImports.default = name;
289+
} else {
290+
if (!moduleImports.named) {
291+
moduleImports.named = new Set();
292+
}
293+
moduleImports.named.add(name);
294+
}
275295
}
276296

277297
// Form import declarations and prepend them to the rest of the contents.
278298
const fullSnippetTypeScriptText = (
279299
[...importMap.entries()]
280-
.map(
281-
([module, symbols]) =>
282-
`import { ${[...symbols.values()].join(", ")} } from "${module}";`,
283-
)
300+
.map(([module, imports]) => {
301+
const importParts = [];
302+
if (imports.default) {
303+
importParts.push(imports.default);
304+
}
305+
if (imports.named) {
306+
importParts.push(`{ ${[...imports.named].join(", ")} }`);
307+
}
308+
309+
if (importParts.length === 0) {
310+
throw new Error(
311+
`unrecoverable error: no imports were generated for the snippet '${name.text}'`,
312+
);
313+
}
314+
315+
return `import ${importParts.join(", ")} from "${module}";`;
316+
})
284317
.join(EOL) +
285318
EOL +
286319
EOL +
@@ -387,11 +420,14 @@ async function parseSnippetDefinitions(
387420
* @param node - the node to check for imports
388421
* @returns a list of module specifiers that form the definition of the node's symbol, or undefined
389422
*/
390-
function extractImportLocations(node: ts.Node): string[] | undefined {
423+
function extractImportLocations(node: ts.Node): {
424+
isDefault: boolean;
425+
moduleSpecifier: string;
426+
}[] {
391427
const sym = checker.getSymbolAtLocation(node);
392428

393429
// Get all the decls that are in source files and where the decl comes from an import clause.
394-
return sym?.declarations
430+
const nonDefaultExports = sym?.declarations
395431
?.filter(
396432
(decl) =>
397433
decl.getSourceFile() === sourceFile &&
@@ -411,12 +447,38 @@ async function parseSnippetDefinitions(
411447
moduleSpecifierText === path.join(relativeIndexPath, "index.js") ||
412448
moduleSpecifierText === path.join(relativeIndexPath, "index")
413449
) {
414-
return project.name;
450+
return { moduleSpecifier: project.name, isDefault: false };
415451
} else {
416-
return moduleSpecifierText;
452+
return { moduleSpecifier: moduleSpecifierText, isDefault: false };
417453
}
418454
},
419455
);
456+
457+
const defaultExports = sym?.declarations
458+
?.filter(
459+
(decl) =>
460+
decl.getSourceFile() === sourceFile &&
461+
ts.isImportClause(decl) &&
462+
ts.isImportDeclaration(decl.parent) &&
463+
decl.name,
464+
)
465+
.map((decl) => {
466+
const moduleSpecifierText = (
467+
(decl.parent as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral
468+
).text;
469+
470+
if (
471+
moduleSpecifierText === relativeIndexPath ||
472+
moduleSpecifierText === path.join(relativeIndexPath, "index.js") ||
473+
moduleSpecifierText === path.join(relativeIndexPath, "index")
474+
) {
475+
return { moduleSpecifier: project.name, isDefault: true };
476+
} else {
477+
return { moduleSpecifier: moduleSpecifierText, isDefault: true };
478+
}
479+
});
480+
481+
return [...(nonDefaultExports ?? []), ...(defaultExports ?? [])];
420482
}
421483
}
422484

sdk/agrifood/agrifood-farming-rest/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET
5454
Use the returned token credential to authenticate the client:
5555

5656
```ts snippet:CreateFarmBeatsClient
57-
import { FarmBeats } from "@azure-rest/agrifood-farming";
57+
import FarmBeats from "@azure-rest/agrifood-farming";
5858
import { DefaultAzureCredential } from "@azure/identity";
5959

6060
const client = FarmBeats(
@@ -96,7 +96,7 @@ Once you have authenticated and created the client object as shown in the [Authe
9696
section, you can create a party within the Data Manager for Agriculture resource like this:
9797

9898
```ts snippet:CreateParty
99-
import { FarmBeats, isUnexpected } from "@azure-rest/agrifood-farming";
99+
import FarmBeats, { isUnexpected } from "@azure-rest/agrifood-farming";
100100
import { DefaultAzureCredential } from "@azure/identity";
101101

102102
const client = FarmBeats(
@@ -127,7 +127,7 @@ console.log(`Created Party: ${party.name}`);
127127
### List Parties
128128

129129
```ts snippet:ListParties
130-
import { FarmBeats, isUnexpected, paginate } from "@azure-rest/agrifood-farming";
130+
import FarmBeats, { isUnexpected, paginate } from "@azure-rest/agrifood-farming";
131131
import { DefaultAzureCredential } from "@azure/identity";
132132

133133
const client = FarmBeats(

sdk/agrifood/agrifood-farming-rest/test/public/farmHeirarchy.spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import type {
88
import { getLongRunningPoller, isUnexpected } from "../../src/index.js";
99
import { createClient, createRecorder } from "./utils/recordedClient.js";
1010
import type { Recorder } from "@azure-tools/test-recorder";
11-
import { isNode } from "@azure/core-util";
11+
import { isNodeLike } from "@azure/core-util";
1212
import { describe, it, assert, beforeEach, afterEach } from "vitest";
1313

1414
const startDateTime = new Date("2020-02-01T08:00:00.000Z");
1515
const endDateTime = new Date("2020-03-02T08:00:00.000Z");
16-
const suffix = isNode ? "node" : "browser";
16+
const suffix = isNodeLike ? "node" : "browser";
1717
const partyId = `${suffix}-contoso-party`;
1818
const boundaryId = `${suffix}-contoso-boundary`;
1919
const testparty = {
@@ -28,13 +28,13 @@ describe("party Operations", () => {
2828
let recorder: Recorder;
2929
let client: FarmBeatsClient;
3030

31-
beforeEach(async function (ctx) {
31+
beforeEach(async (ctx) => {
3232
recorder = await createRecorder(ctx);
3333
client = createClient(recorder.configureClientOptions({}));
3434
jobId = recorder.variable("jobId", `${suffix}-job-${Math.ceil(Math.random() * 1000)}`);
3535
});
3636

37-
afterEach(async function () {
37+
afterEach(async () => {
3838
await recorder.stop();
3939
});
4040

sdk/agrifood/agrifood-farming-rest/test/snippets.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { describe, it } from "vitest";
5-
import { FarmBeats, isUnexpected, paginate } from "@azure-rest/agrifood-farming";
5+
import FarmBeats, { isUnexpected, paginate } from "../src/index.js";
66
import { DefaultAzureCredential } from "@azure/identity";
77
import { setLogLevel } from "@azure/logger";
88

sdk/anomalydetector/ai-anomaly-detector-rest/README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ The following section provides several code snippets covering some of the most c
9090
### Batch detection
9191

9292
```ts snippet:batch_detection
93-
import {
93+
import AnomalyDetector, {
9494
TimeSeriesPoint,
95-
AnomalyDetector,
9695
DetectUnivariateEntireSeriesParameters,
9796
isUnexpected,
9897
} from "@azure-rest/ai-anomaly-detector";
98+
import { readFileSync } from "node:fs";
9999
import { parse } from "csv-parse/sync";
100100
import { AzureKeyCredential } from "@azure/core-auth";
101101

@@ -105,7 +105,7 @@ const timeSeriesDataPath = "./samples-dev/example-data/request-data.csv";
105105

106106
function read_series_from_file(path: string): Array<TimeSeriesPoint> {
107107
const result = Array<TimeSeriesPoint>();
108-
const input = fs.readFileSync(path).toString();
108+
const input = readFileSync(path).toString();
109109
const parsed = parse(input, { skip_empty_lines: true });
110110
parsed.forEach(function (e: Array<string>) {
111111
result.push({ timestamp: new Date(e[0]), value: Number(e[1]) });
@@ -149,12 +149,12 @@ if (result.body.isAnomaly) {
149149
### Streaming Detection
150150

151151
```ts snippet:streaming_detection
152-
import {
152+
import AnomalyDetector, {
153153
TimeSeriesPoint,
154-
AnomalyDetector,
155154
DetectUnivariateLastPointParameters,
156155
isUnexpected,
157156
} from "@azure-rest/ai-anomaly-detector";
157+
import { readFileSync } from "node:fs";
158158
import { parse } from "csv-parse/sync";
159159
import { AzureKeyCredential } from "@azure/core-auth";
160160

@@ -164,7 +164,7 @@ const timeSeriesDataPath = "./samples-dev/example-data/request-data.csv";
164164

165165
function read_series_from_file(path: string): Array<TimeSeriesPoint> {
166166
const result = Array<TimeSeriesPoint>();
167-
const input = fs.readFileSync(path).toString();
167+
const input = readFileSync(path).toString();
168168
const parsed = parse(input, { skip_empty_lines: true });
169169
parsed.forEach(function (e: Array<string>) {
170170
result.push({ timestamp: new Date(e[0]), value: Number(e[1]) });
@@ -205,12 +205,12 @@ if (result.body.isAnomaly) {
205205
### Detect change points
206206

207207
```ts snippet:detect_change_points
208-
import {
208+
import AnomalyDetector, {
209209
TimeSeriesPoint,
210-
AnomalyDetector,
211210
DetectUnivariateChangePointParameters,
212211
isUnexpected,
213212
} from "@azure-rest/ai-anomaly-detector";
213+
import { readFileSync } from "node:fs";
214214
import { parse } from "csv-parse/sync";
215215
import { AzureKeyCredential } from "@azure/core-auth";
216216

@@ -220,7 +220,7 @@ const timeSeriesDataPath = "./samples-dev/example-data/request-data.csv";
220220

221221
function read_series_from_file(path: string): Array<TimeSeriesPoint> {
222222
const result = Array<TimeSeriesPoint>();
223-
const input = fs.readFileSync(path).toString();
223+
const input = readFileSync(path).toString();
224224
const parsed = parse(input, { skip_empty_lines: true });
225225
parsed.forEach(function (e: Array<string>) {
226226
result.push({ timestamp: new Date(e[0]), value: Number(e[1]) });

sdk/anomalydetector/ai-anomaly-detector-rest/test/snippets.spec.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
// Licensed under the MIT License.
33

44
import { describe, it } from "vitest";
5-
import fs from "node:fs";
5+
import { readFileSync } from "node:fs";
66
import { parse } from "csv-parse/sync";
77
import { AzureKeyCredential } from "@azure/core-auth";
88
import type {
99
DetectUnivariateChangePointParameters,
1010
DetectUnivariateEntireSeriesParameters,
1111
DetectUnivariateLastPointParameters,
1212
TimeSeriesPoint,
13-
} from "@azure-rest/ai-anomaly-detector";
14-
import { AnomalyDetector, isUnexpected } from "@azure-rest/ai-anomaly-detector";
13+
} from "../src/index.js";
14+
import AnomalyDetector, { isUnexpected } from "../src/index.js";
1515
import { setLogLevel } from "@azure/logger";
1616

1717
describe("snippets", () => {
@@ -22,7 +22,7 @@ describe("snippets", () => {
2222
// @ts-preserve-whitespace
2323
function read_series_from_file(path: string): Array<TimeSeriesPoint> {
2424
const result = Array<TimeSeriesPoint>();
25-
const input = fs.readFileSync(path).toString();
25+
const input = readFileSync(path).toString();
2626
const parsed = parse(input, { skip_empty_lines: true });
2727
parsed.forEach(function (e: Array<string>) {
2828
result.push({ timestamp: new Date(e[0]), value: Number(e[1]) });
@@ -70,7 +70,7 @@ describe("snippets", () => {
7070
// @ts-preserve-whitespace
7171
function read_series_from_file(path: string): Array<TimeSeriesPoint> {
7272
const result = Array<TimeSeriesPoint>();
73-
const input = fs.readFileSync(path).toString();
73+
const input = readFileSync(path).toString();
7474
const parsed = parse(input, { skip_empty_lines: true });
7575
parsed.forEach(function (e: Array<string>) {
7676
result.push({ timestamp: new Date(e[0]), value: Number(e[1]) });
@@ -115,7 +115,7 @@ describe("snippets", () => {
115115
// @ts-preserve-whitespace
116116
function read_series_from_file(path: string): Array<TimeSeriesPoint> {
117117
const result = Array<TimeSeriesPoint>();
118-
const input = fs.readFileSync(path).toString();
118+
const input = readFileSync(path).toString();
119119
const parsed = parse(input, { skip_empty_lines: true });
120120
parsed.forEach(function (e: Array<string>) {
121121
result.push({ timestamp: new Date(e[0]), value: Number(e[1]) });

sdk/appservice/arm-appservice-profile-2020-09-01-hybrid/LICENSE.txt

-21
This file was deleted.

0 commit comments

Comments
 (0)