|
1 | | -import { SourceFile, SyntaxKind } from "ts-morph"; |
2 | | -import shell from 'shelljs'; |
3 | | -import path from 'path'; |
| 1 | +import { SourceFile, SyntaxKind } from "ts-morph"; |
| 2 | +import shell from "shelljs"; |
| 3 | +import path from "path"; |
| 4 | +import * as ts from "typescript"; |
4 | 5 |
|
5 | | -import { ApiVersionType } from "../../common/types" |
| 6 | +import { ApiVersionType } from "../../common/types"; |
6 | 7 | import { IApiVersionTypeExtractor } from "../../common/interfaces"; |
7 | 8 | import { getTsSourceFile } from "../../common/utils"; |
| 9 | +import { readFileSync } from "fs"; |
8 | 10 |
|
9 | 11 | const findRestClientPath = (packageRoot: string): string => { |
10 | | - const restPath = path.join(packageRoot, 'src/rest/'); |
| 12 | + const restPath = path.join(packageRoot, "src/rest/"); |
11 | 13 | const fileNames = shell.ls(restPath); |
12 | | - const clientFiles = fileNames.filter(f => f.endsWith("Client.ts")); |
13 | | - if (clientFiles.length !== 1) throw new Error(`Single client is supported, but found ${clientFiles}`); |
| 14 | + const clientFiles = fileNames.filter((f) => f.endsWith("Client.ts")); |
| 15 | + if (clientFiles.length !== 1) |
| 16 | + throw new Error(`Single client is supported, but found "${clientFiles}" in ${restPath}`); |
14 | 17 |
|
15 | 18 | const clientPath = path.join(restPath, clientFiles[0]); |
16 | 19 | return clientPath; |
17 | 20 | }; |
18 | 21 |
|
19 | | -const matchPattern = (text: string, pattern: RegExp): string | undefined => { |
20 | | - const match = text.match(pattern); |
21 | | - const found = match != null && match.length === 2; |
22 | | - return found ? match?.at(1) : undefined; |
23 | | -} |
24 | | - |
25 | | -const findApiVersionInRestClient = (clientPath: string): string | undefined => { |
| 22 | +const findApiVersionInRestClientV1 = ( |
| 23 | + clientPath: string |
| 24 | +): string | undefined => { |
26 | 25 | const sourceFile = getTsSourceFile(clientPath); |
27 | 26 | const createClientFunction = sourceFile?.getFunction("createClient"); |
28 | | - if (!createClientFunction) throw new Error("Function 'createClient' not found."); |
| 27 | + if (!createClientFunction) |
| 28 | + throw new Error("Function 'createClient' not found."); |
29 | 29 |
|
30 | | - const apiVersionStatements = createClientFunction.getStatements() |
31 | | - .filter(s => |
32 | | - s.getKind() === SyntaxKind.ExpressionStatement && |
33 | | - s.getText().indexOf("options.apiVersion") > -1); |
34 | | - if (apiVersionStatements.length === 0) return undefined; |
| 30 | + const apiVersionStatements = createClientFunction |
| 31 | + .getStatements() |
| 32 | + .filter((s) => s.getText().includes("options.apiVersion")); |
| 33 | + if (apiVersionStatements.length === 0) { |
| 34 | + return undefined; |
| 35 | + } |
| 36 | + const text = |
| 37 | + apiVersionStatements[apiVersionStatements.length - 1].getText(); |
| 38 | + return extractApiVersionFromText(text); |
| 39 | +}; |
35 | 40 |
|
36 | | - const text = apiVersionStatements[apiVersionStatements.length - 1].getText(); |
37 | | - const pattern = /(\d{4}-\d{2}-\d{2}(?:-preview)?)/; |
38 | | - const apiVersion = matchPattern(text, pattern); |
| 41 | +const extractApiVersionFromText = (text: string): string | undefined => { |
| 42 | + const begin = text.indexOf('"'); |
| 43 | + const end = text.lastIndexOf('"'); |
| 44 | + return text.substring(begin + 1, end); |
| 45 | +}; |
| 46 | + |
| 47 | +// new ways in @autorest/typespec-ts emitter to set up api-version |
| 48 | +const findApiVersionInRestClientV2 = (clientPath: string): string | undefined => { |
| 49 | + const sourceCode= readFileSync(clientPath, {encoding: 'utf-8'}) |
| 50 | + const sourceFile = ts.createSourceFile("example.ts", sourceCode, ts.ScriptTarget.Latest, true); |
| 51 | + const createClientFunction = sourceFile.statements.filter(s => (s as ts.FunctionDeclaration)?.name?.escapedText === 'createClient').map(s => (s as ts.FunctionDeclaration))[0]; |
| 52 | + let apiVersion: string | undefined = undefined; |
| 53 | + createClientFunction.parameters.forEach(p => { |
| 54 | + const isBindingPattern = node => node && typeof node === "object" && "elements" in node && "parent" in node && "kind" in node; |
| 55 | + if (!isBindingPattern(p.name)) { |
| 56 | + return; |
| 57 | + } |
| 58 | + const binding = p.name as ts.ObjectBindingPattern; |
| 59 | + const apiVersionTexts = binding.elements?.filter(e => (e.name as ts.Identifier)?.escapedText === "apiVersion").map(e => e.initializer?.getText()); |
| 60 | + // apiVersionTexts.length must be 0 or 1, otherwise the binding pattern contains the same keys, which causes a ts error |
| 61 | + if (apiVersionTexts.length === 1 && apiVersionTexts[0]) { |
| 62 | + apiVersion = extractApiVersionFromText(apiVersionTexts[0]); |
| 63 | + } |
| 64 | + }); |
39 | 65 | return apiVersion; |
40 | 66 | }; |
41 | 67 |
|
42 | | -const getApiVersionTypeFromRestClient: IApiVersionTypeExtractor = (packageRoot: string): ApiVersionType => { |
| 68 | +// workaround for createClient function changes it's way to setup api-version |
| 69 | +export const findApiVersionInRestClient = (clientPath: string): string | undefined => { |
| 70 | + const version2 = findApiVersionInRestClientV2(clientPath); |
| 71 | + if (version2) { |
| 72 | + return version2; |
| 73 | + } |
| 74 | + const version1 = findApiVersionInRestClientV1(clientPath); |
| 75 | + return version1; |
| 76 | +}; |
| 77 | + |
| 78 | +const getApiVersionTypeFromRestClient: IApiVersionTypeExtractor = ( |
| 79 | + packageRoot: string |
| 80 | +): ApiVersionType => { |
43 | 81 | const clientPath = findRestClientPath(packageRoot); |
44 | 82 | const apiVersion = findApiVersionInRestClient(clientPath); |
45 | | - if (apiVersion && apiVersion.indexOf("-preview") >= 0) return ApiVersionType.Preview; |
46 | | - if (apiVersion && apiVersion.indexOf("-preview") < 0) return ApiVersionType.Stable; |
| 83 | + if (apiVersion && apiVersion.indexOf("-preview") >= 0) |
| 84 | + return ApiVersionType.Preview; |
| 85 | + if (apiVersion && apiVersion.indexOf("-preview") < 0) |
| 86 | + return ApiVersionType.Stable; |
47 | 87 | return ApiVersionType.None; |
48 | 88 | }; |
49 | 89 |
|
50 | | -const findApiVersionsInOperations = (sourceFile: SourceFile | undefined): Array<string> | undefined => { |
| 90 | +const findApiVersionsInOperations = ( |
| 91 | + sourceFile: SourceFile | undefined |
| 92 | +): Array<string> | undefined => { |
51 | 93 | const interfaces = sourceFile?.getInterfaces(); |
52 | | - const interfacesWithApiVersion = interfaces?.filter(itf => itf.getProperty('"api-version"')); |
53 | | - const apiVersions = interfacesWithApiVersion?.map(itf => { |
54 | | - const property = itf.getMembers() |
55 | | - .filter(m => { |
56 | | - const defaultValue = m.getChildrenOfKind(SyntaxKind.StringLiteral)[0]; |
57 | | - return defaultValue && defaultValue.getText() === '"api-version"'; |
58 | | - })[0]; |
59 | | - const apiVersion = property.getChildrenOfKind(SyntaxKind.LiteralType)[0].getText(); |
| 94 | + const interfacesWithApiVersion = interfaces?.filter((itf) => |
| 95 | + itf.getProperty('"api-version"') |
| 96 | + ); |
| 97 | + const apiVersions = interfacesWithApiVersion?.map((itf) => { |
| 98 | + const property = itf.getMembers().filter((m) => { |
| 99 | + const defaultValue = m.getChildrenOfKind( |
| 100 | + SyntaxKind.StringLiteral |
| 101 | + )[0]; |
| 102 | + return defaultValue && defaultValue.getText() === '"api-version"'; |
| 103 | + })[0]; |
| 104 | + const apiVersion = property |
| 105 | + .getChildrenOfKind(SyntaxKind.LiteralType)[0] |
| 106 | + .getText(); |
60 | 107 | return apiVersion; |
61 | 108 | }); |
62 | 109 | return apiVersions; |
63 | | -} |
| 110 | +}; |
64 | 111 |
|
65 | | -const getApiVersionTypeFromOperations: IApiVersionTypeExtractor = (packageRoot: string): ApiVersionType => { |
66 | | - const paraPath = path.join(packageRoot, 'src/rest/parameters.ts'); |
| 112 | +const getApiVersionTypeFromOperations: IApiVersionTypeExtractor = ( |
| 113 | + packageRoot: string |
| 114 | +): ApiVersionType => { |
| 115 | + const paraPath = path.join(packageRoot, "src/rest/parameters.ts"); |
67 | 116 | const sourceFile = getTsSourceFile(paraPath); |
68 | 117 | const apiVersions = findApiVersionsInOperations(sourceFile); |
69 | 118 | if (!apiVersions) return ApiVersionType.None; |
70 | | - const previewVersions = apiVersions.filter(v => v.indexOf("-preview") >= 0); |
71 | | - return previewVersions.length > 0 ? ApiVersionType.Preview : ApiVersionType.Stable; |
| 119 | + const previewVersions = apiVersions.filter( |
| 120 | + (v) => v.indexOf("-preview") >= 0 |
| 121 | + ); |
| 122 | + return previewVersions.length > 0 |
| 123 | + ? ApiVersionType.Preview |
| 124 | + : ApiVersionType.Stable; |
72 | 125 | }; |
73 | 126 |
|
74 | | -// TODO: add unit test |
75 | | -export const getApiVersionType: IApiVersionTypeExtractor = (packageRoot: string): ApiVersionType => { |
| 127 | +export const getApiVersionType: IApiVersionTypeExtractor = ( |
| 128 | + packageRoot: string |
| 129 | +): ApiVersionType => { |
76 | 130 | const typeFromClient = getApiVersionTypeFromRestClient(packageRoot); |
77 | 131 | if (typeFromClient !== ApiVersionType.None) return typeFromClient; |
78 | 132 | const typeFromOperations = getApiVersionTypeFromOperations(packageRoot); |
79 | 133 | if (typeFromOperations !== ApiVersionType.None) return typeFromOperations; |
80 | 134 | return ApiVersionType.Stable; |
81 | | -} |
| 135 | +}; |
0 commit comments