зеркало из
1
0
Форкнуть 0
This commit is contained in:
Timothee Guerin 2020-12-03 13:43:28 -08:00
Родитель 666fb77fef
Коммит 1ce2f33612
38 изменённых файлов: 2813 добавлений и 2357 удалений

Просмотреть файл

@ -47,4 +47,4 @@ rules:
- warn
- max: 2
maxBOF: 0
maxEOF: 1
maxEOF: 1

Просмотреть файл

@ -2,4 +2,4 @@ trailingComma: "all"
printWidth: 120
quoteProps: "consistent"
endOfLine: "auto"
arrowParens: always
arrowParens: always

Просмотреть файл

@ -3,33 +3,33 @@ trigger: none
pr: none
pool:
vmImage: 'ubuntu-latest'
vmImage: "ubuntu-latest"
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- task: NodeTool@0
inputs:
versionSpec: "12.x"
displayName: "Install Node.js"
- script: |
npm install -g npm
npm cache clear --force
- script: |
npm install -g npm
npm cache clear --force
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
displayName: 'Build Modelerfour'
displayName: "Build Modelerfour"
- script: |
npm install -g autorest
git clone --no-tags --depth=1 https://github.com/Azure/azure-rest-api-specs
node .scripts/run-benchmark.js ./azure-rest-api-specs ./output
displayName: Benchmark Azure REST API Specs
- script: |
npm install -g autorest
git clone --no-tags --depth=1 https://github.com/Azure/azure-rest-api-specs
node .scripts/run-benchmark.js ./azure-rest-api-specs ./output
displayName: Benchmark Azure REST API Specs
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(System.DefaultWorkingDirectory)/output'
artifactName: benchmark-output
displayName: 'Upload Results'
- task: PublishPipelineArtifact@1
inputs:
targetPath: "$(System.DefaultWorkingDirectory)/output"
artifactName: benchmark-output
displayName: "Upload Results"

Просмотреть файл

@ -1,6 +1,6 @@
const { spawn } = require('child_process');
const { readFileSync } = require('fs');
const { resolve } = require('path');
const { spawn } = require("child_process");
const { readFileSync } = require("fs");
const { resolve } = require("path");
function read(filename) {
const txt = readFileSync(filename, "utf8")
@ -17,8 +17,6 @@ const repo = `${__dirname}/..`;
const rush = read(`${repo}/rush.json`);
const pjs = {};
function forEachProject(onEach) {
// load all the projects
for (const each of rush.projects) {
@ -42,28 +40,33 @@ function npmForEach(cmd) {
const proc = spawn("npm", ["--silent", "run", cmd], { cwd: location, shell: true, stdio: "inherit" });
procs.push(proc);
result[name] = {
name, location, project, proc,
name,
location,
project,
proc,
};
}
});
procs.forEach(proc => proc.on("close", (code, signal) => {
count--;
exitCode += code;
procs.forEach((proc) =>
proc.on("close", (code, signal) => {
count--;
exitCode += code;
if (count === 0) {
const t2 = process.uptime() * 100;
if (count === 0) {
const t2 = process.uptime() * 100;
console.log('---------------------------------------------------------');
if (exitCode !== 0) {
console.log(` Done : command '${cmd}' - ${Math.floor(t2 - t1) / 100} s -- Errors ${exitCode} `)
} else {
console.log(` Done : command '${cmd}' - ${Math.floor(t2 - t1) / 100} s -- No Errors `)
console.log("---------------------------------------------------------");
if (exitCode !== 0) {
console.log(` Done : command '${cmd}' - ${Math.floor(t2 - t1) / 100} s -- Errors ${exitCode} `);
} else {
console.log(` Done : command '${cmd}' - ${Math.floor(t2 - t1) / 100} s -- No Errors `);
}
console.log("---------------------------------------------------------");
process.exit(exitCode);
}
console.log('---------------------------------------------------------');
process.exit(exitCode);
}
}));
}),
);
return result;
}

Просмотреть файл

@ -1,2 +1,2 @@
// Runs the npm run command on each project that has it.
require('./for-each').npm(process.argv[2]);
require("./for-each").npm(process.argv[2]);

Просмотреть файл

@ -4,59 +4,58 @@
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
- master
- master
pool:
vmImage: 'ubuntu-latest'
vmImage: "ubuntu-latest"
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: NodeTool@0
inputs:
versionSpec: "10.x"
displayName: "Install Node.js"
- script: |
# ensure latest npm is installed
npm install -g npm
npm install -g publish-release
- script: |
# ensure latest npm is installed
npm install -g npm
npm install -g publish-release
# make sure the versions are all synchronized and pull in dependencies
npx @microsoft/rush sync-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# make sure the versions are all synchronized and pull in dependencies
npx @microsoft/rush sync-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# set the actual package versions and update again
npx @microsoft/rush set-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# set the actual package versions and update again
npx @microsoft/rush set-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush sync-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush sync-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# compile the code
npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# compile the code
npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# build the packages
npx @microsoft/rush publish --publish --pack --include-all --tag latest
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# build the packages
npx @microsoft/rush publish --publish --pack --include-all --tag latest
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# publish autorest cli and beta (@autorest/autorest)
cd modelerfour
v=`node -e "console.log(require('./package.json').version)"`
publish-release --token $(azuresdk-github-pat) --repo autorest.modelerfour --owner azure --name v$v --tag v$v --notes='prerelease build' --prerelease --editRelease false --assets ../common/temp/artifacts/packages/autorest-modelerfour-$v.tgz --target_commitish $(Build.SourceBranchName)
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
cd ..
# publish the packages (tag as preview by default)
# echo "//registry.npmjs.org/:_authToken=$(azure-sdk-npm-token)" > ./.npmrc
# for file in common/temp/artifacts/packages/*.tgz
# do
# common/temp/pnpm-local/node_modules/.bin/pnpm publish $file --tag latest --access public || echo no-worries
# done
# publish autorest cli and beta (@autorest/autorest)
cd modelerfour
v=`node -e "console.log(require('./package.json').version)"`
publish-release --token $(azuresdk-github-pat) --repo autorest.modelerfour --owner azure --name v$v --tag v$v --notes='prerelease build' --prerelease --editRelease false --assets ../common/temp/artifacts/packages/autorest-modelerfour-$v.tgz --target_commitish $(Build.SourceBranchName)
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
cd ..
# publish the packages (tag as preview by default)
# echo "//registry.npmjs.org/:_authToken=$(azure-sdk-npm-token)" > ./.npmrc
# for file in common/temp/artifacts/packages/*.tgz
# do
# common/temp/pnpm-local/node_modules/.bin/pnpm publish $file --tag latest --access public || echo no-worries
# done

Просмотреть файл

@ -5,59 +5,59 @@ debug: false
specs:
- specRootPath: ../modelerfour/node_modules/@microsoft.azure/autorest.testserver/swagger
specPaths:
- additionalProperties.json
- azure-parameter-grouping.json
- azure-report.json
- azure-resource-x.json
- azure-resource.json
- azure-special-properties.json
- body-array.json
- body-boolean.json
- body-boolean.quirks.json
- body-byte.json
- body-complex.json
- body-date.json
- body-datetime-rfc1123.json
- body-datetime.json
- body-duration.json
- body-dictionary.json
- body-file.json
# Not yet supported by AutoRest v3 and Modelerfour
# - body-formdata-urlencoded.json
# - body-formdata.json
- body-integer.json
- body-number.json
- body-number.quirks.json
- body-string.json
- body-string.quirks.json
- complex-model.json
- custom-baseUrl.json
- custom-baseUrl-more-options.json
- custom-baseUrl-paging.json
- extensible-enums-swagger.json
- head-exceptions.json
- head.json
- header.json
- httpInfrastructure.json
- httpInfrastructure.quirks.json
- lro.json
- media_types.json
- model-flattening.json
- multiapi-v1.json
- multiapi-v2.json
- multiapi-v3.json
- object-type.json
- paging.json
- parameter-flattening.json
- report.json
- required-optional.json
- storage.json
- subscriptionId-apiVersion.json
- url-multi-collectionFormat.json
- url.json
- validation.json
- xml-service.json
- xms-error-responses.json
- additionalProperties.json
- azure-parameter-grouping.json
- azure-report.json
- azure-resource-x.json
- azure-resource.json
- azure-special-properties.json
- body-array.json
- body-boolean.json
- body-boolean.quirks.json
- body-byte.json
- body-complex.json
- body-date.json
- body-datetime-rfc1123.json
- body-datetime.json
- body-duration.json
- body-dictionary.json
- body-file.json
# Not yet supported by AutoRest v3 and Modelerfour
# - body-formdata-urlencoded.json
# - body-formdata.json
- body-integer.json
- body-number.json
- body-number.quirks.json
- body-string.json
- body-string.quirks.json
- complex-model.json
- custom-baseUrl.json
- custom-baseUrl-more-options.json
- custom-baseUrl-paging.json
- extensible-enums-swagger.json
- head-exceptions.json
- head.json
- header.json
- httpInfrastructure.json
- httpInfrastructure.quirks.json
- lro.json
- media_types.json
- model-flattening.json
- multiapi-v1.json
- multiapi-v2.json
- multiapi-v3.json
- object-type.json
- paging.json
- parameter-flattening.json
- report.json
- required-optional.json
- storage.json
- subscriptionId-apiVersion.json
- url-multi-collectionFormat.json
- url.json
- validation.json
- xml-service.json
- xms-error-responses.json
languages:
- language: python
outputPath: ../modelerfour/test/regression/python
@ -73,7 +73,7 @@ languages:
outputPath: ../modelerfour/test/regression/typescript
excludeSpecs:
# The paging spec currently fails, will re-enable once it's fixed
- paging.json
- paging.json
oldArgs:
- --v3
- --package-name:test-package

Просмотреть файл

@ -32,10 +32,7 @@ function executeBenchmark(readmePath) {
fs.mkdirSync(outputPath, { recursive: true });
const fileStream = fs.openSync(
path.join(outputPath, "autorest-output.txt"),
"w"
);
const fileStream = fs.openSync(path.join(outputPath, "autorest-output.txt"), "w");
console.log(relativePath);
@ -53,7 +50,7 @@ function executeBenchmark(readmePath) {
--inspector.output-folder="./${outputPath}" \
--output-folder="./${outputPath}" \
"${readmePath}"`,
{ stdio: ["inherit", fileStream, fileStream] }
{ stdio: ["inherit", fileStream, fileStream] },
);
} catch (err) {
result = "failed";
@ -61,16 +58,14 @@ function executeBenchmark(readmePath) {
}
const elapsedTime = process.hrtime(startTime);
const elapsedSeconds = (elapsedTime[0] + elapsedTime[1] / 1000000000).toFixed(
3
);
const elapsedSeconds = (elapsedTime[0] + elapsedTime[1] / 1000000000).toFixed(3);
console.log(`${resultColor}${result} in ${elapsedSeconds}`, "\033[0m\n");
return {
specPath: readmePath,
outputPath,
succeeded: result === "succeeded",
time: parseFloat(elapsedSeconds)
time: parseFloat(elapsedSeconds),
};
}
@ -79,9 +74,7 @@ console.log("");
const readmePaths = getReadmesRecursively(specsPath);
const results = readmePaths
.map(executeBenchmark)
.sort((a, b) => b.time - a.time);
const results = readmePaths.map(executeBenchmark).sort((a, b) => b.time - a.time);
let aggregate = results.reduce(
(totals, result) => {
@ -89,31 +82,23 @@ let aggregate = results.reduce(
totals.time += result.time;
return totals;
},
{ time: 0.0, success: 0 }
{ time: 0.0, success: 0 },
);
const successPercentage = ((aggregate.success / results.length) * 100).toFixed(
2
);
const successPercentage = ((aggregate.success / results.length) * 100).toFixed(2);
console.log(
`${aggregate.success} out of ${
results.length
} succeeded (${successPercentage}%), ${aggregate.time.toFixed(
3
)}s total time\n`
`${aggregate.success} out of ${results.length} succeeded (${successPercentage}%), ${aggregate.time.toFixed(
3,
)}s total time\n`,
);
const topCount = Math.min(5, results.length);
console.log(`Top ${topCount} longest runs:\n`);
results
.slice(0, topCount)
.forEach(r => console.log(`${r.time}s ${r.specPath}`));
results.slice(0, topCount).forEach((r) => console.log(`${r.time}s ${r.specPath}`));
// Write out aggregate results file
const resultsFile = fs.writeFileSync(
path.join(outputBasePath, "autorest-benchmark-results.csv"),
results
.map(r => `${r.specPath},${r.succeeded ? "succeeded" : "failed"},${r.time}`)
.join("\n")
results.map((r) => `${r.specPath},${r.succeeded ? "succeeded" : "failed"},${r.time}`).join("\n"),
);

Просмотреть файл

@ -1,44 +1,42 @@
const { exec } = require('child_process');
const { writeFileSync } = require('fs');
const { forEachProject, projectCount } = require('./for-each');
const { exec } = require("child_process");
const { writeFileSync } = require("fs");
const { forEachProject, projectCount } = require("./for-each");
let count = projectCount;
function updateVersion(name, project, location, patch) {
const origJson = JSON.stringify(project, null, 2);
// update the third digit
const verInfo = project.version.split('.');
const verInfo = project.version.split(".");
verInfo[2] = patch;
project.version = verInfo.join('.');
project.version = verInfo.join(".");
// write the file if it's changed
const newJson = JSON.stringify(project, null, 2);
if (origJson !== newJson) {
console.log(`Writing project '${name}' version to '${project.version}' in '${location}'`);
writeFileSync(`${location}/package.json`, newJson)
writeFileSync(`${location}/package.json`, newJson);
}
count--;
if (count === 0) {
// last one!
// call sync-versions
require('./sync-versions');
require("./sync-versions");
}
}
if (process.argv[2] === '--reset') {
if (process.argv[2] === "--reset") {
forEachProject((name, location, project) => {
updateVersion(name, project, location, 0);
})
});
} else {
// Sets the patch version on each package.json in the project.
forEachProject((name, location, project) => {
exec(`git rev-list --parents HEAD --count --full-history .`, { cwd: location }, (o, stdout) => {
const patch = (parseInt(stdout.trim()) + (Number(project.patchOffset) || -1));
const patch = parseInt(stdout.trim()) + (Number(project.patchOffset) || -1);
updateVersion(name, project, location, patch);
});
});
}

Просмотреть файл

@ -1,12 +1,12 @@
const { readFileSync, writeFileSync } = require('fs');
const { readFileSync, writeFileSync } = require("fs");
function read(filename) {
const txt = readFileSync(filename, 'utf8')
.replace(/\r/gm, '')
.replace(/\n/gm, '«')
.replace(/\/\*.*?\*\//gm, '')
.replace(/«/gm, '\n')
.replace(/\s+\/\/.*/g, '');
const txt = readFileSync(filename, "utf8")
.replace(/\r/gm, "")
.replace(/\n/gm, "«")
.replace(/\/\*.*?\*\//gm, "")
.replace(/«/gm, "\n")
.replace(/\s+\/\/.*/g, "");
return JSON.parse(txt);
}
@ -15,26 +15,29 @@ const rush = read(`${__dirname}/../rush.json`);
const pjs = {};
function writeIfChanged(filename, content) {
const orig = JSON.parse(readFileSync(filename))
const orig = JSON.parse(readFileSync(filename));
const origJson = JSON.stringify(orig, null, 2);
const json = JSON.stringify(content, null, 2);
if (origJson !== json) {
console.log(`Writing updated file '${filename}'`)
writeFileSync(filename, json)
console.log(`Writing updated file '${filename}'`);
writeFileSync(filename, json);
return true;
}
return false;
}
function versionToInt(ver) {
let v = ver.replace(/[^\d\.]/g, '').split('.').slice(0, 3);
let v = ver
.replace(/[^\d\.]/g, "")
.split(".")
.slice(0, 3);
while (v.length < 3) {
v.unshift(0);
}
let n = 0;
for (let i = 0; i < v.length; i++) {
n = n + ((2 ** (i * 16)) * parseInt(v[v.length - 1 - i]))
n = n + 2 ** (i * 16) * parseInt(v[v.length - 1 - i]);
}
return n;
}
@ -51,7 +54,6 @@ function setPeerDependencies(dependencies) {
}
}
function recordDeps(dependencies) {
for (const packageName in dependencies) {
const packageVersion = dependencies[packageName];
@ -71,7 +73,6 @@ function recordDeps(dependencies) {
}
const v2 = versionToInt(packageList[packageName]);
if (v > v2) {
packageList[packageName] = packageVersion;
}
} else {
@ -82,7 +83,7 @@ function recordDeps(dependencies) {
function fixDeps(pj, dependencies) {
for (const packageName in dependencies) {
if (dependencies[packageName] !== packageList[packageName]) {
console.log(`updating ${pj}:${packageName} from '${dependencies[packageName]}' to '${packageList[packageName]}'`)
console.log(`updating ${pj}:${packageName} from '${dependencies[packageName]}' to '${packageList[packageName]}'`);
dependencies[packageName] = packageList[packageName];
}
}
@ -100,8 +101,8 @@ for (const pj of Object.getOwnPropertyNames(pjs)) {
const each = pjs[pj];
setPeerDependencies(each.dependencies);
setPeerDependencies(each.devDependencies);
if (each['static-link']) {
setPeerDependencies(each['static-link'].dependencies);
if (each["static-link"]) {
setPeerDependencies(each["static-link"].dependencies);
}
}
@ -111,8 +112,8 @@ for (const pj of Object.getOwnPropertyNames(pjs)) {
const each = pjs[pj];
recordDeps(each.dependencies);
recordDeps(each.devDependencies);
if (each['static-link']) {
recordDeps(each['static-link'].dependencies);
if (each["static-link"]) {
recordDeps(each["static-link"].dependencies);
}
}
@ -120,8 +121,8 @@ for (const pj of Object.getOwnPropertyNames(pjs)) {
const each = pjs[pj];
fixDeps(pj, each.dependencies);
fixDeps(pj, each.devDependencies);
if (each['static-link']) {
fixDeps(pj, each['static-link'].dependencies);
if (each["static-link"]) {
fixDeps(pj, each["static-link"].dependencies);
}
}
var changed = 0;
@ -138,5 +139,5 @@ for (const each of rush.projects) {
if (changed) {
console.log(`Updated ${changed} files.`);
} else {
console.log('No changes made')
}
console.log("No changes made");
}

Просмотреть файл

@ -4,92 +4,92 @@
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
- master
- master
pool:
vmImage: 'ubuntu-latest'
vmImage: "ubuntu-latest"
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
displayName: 'Install Node.js'
- script: |
npm install -g npm
npm cache clear --force
- task: NodeTool@0
inputs:
versionSpec: "10.x"
displayName: "Install Node.js"
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush test
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
- script: |
npm install -g npm
npm cache clear --force
# check to see if any files changed after the test run.
git status
npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush test
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# set the actual package versions
npx @microsoft/rush set-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# check to see if any files changed after the test run.
git status
npx @microsoft/rush publish --publish --pack --include-all --tag latest
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# set the actual package versions
npx @microsoft/rush set-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
v=`node -e "console.log(require('./modelerfour/package.json').version)"`
echo "##vso[task.setvariable variable=artver]$v"
npx @microsoft/rush publish --publish --pack --include-all --tag latest
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
displayName: 'Rush install, build and test'
v=`node -e "console.log(require('./modelerfour/package.json').version)"`
echo "##vso[task.setvariable variable=artver]$v"
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Build.SourcesDirectory)/common/temp/artifacts/packages/autorest-modelerfour-$(artver).tgz'
artifact: 'packages'
publishLocation: 'pipeline'
displayName: "Rush install, build and test"
- pwsh: |
$url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds/$(Build.BuildId)/artifacts?artifactName=packages&api-version=5.1"
$buildPipeline= Invoke-RestMethod -Uri $url -Headers @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } -Method Get
$ArtifactDownloadURL= $buildPipeline.resource.downloadUrl
$ArtifactDownloadURL = $ArtifactDownloadURL -replace "format=zip","format=file&subPath=%2Fautorest-modelerfour-$(artver).tgz"
echo "Raw Artifact Url: '$ArtifactDownloadURL'"
$downurl = (iwr "http://tinyurl.com/api-create.php?url=$( [System.Web.HttpUtility]::UrlEncode($ArtifactDownloadURL))" ).Content
echo "##vso[task.setvariable variable=DOWNURL]$downurl"
- task: PublishPipelineArtifact@1
inputs:
targetPath: "$(Build.SourcesDirectory)/common/temp/artifacts/packages/autorest-modelerfour-$(artver).tgz"
artifact: "packages"
publishLocation: "pipeline"
- task: GitHubComment@0
inputs:
gitHubConnection: 'Azure'
repositoryName: '$(Build.Repository.Name)'
comment: |
You may test this build by running `autorest --reset` and then either:
<hr>
- pwsh: |
$url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds/$(Build.BuildId)/artifacts?artifactName=packages&api-version=5.1"
$buildPipeline= Invoke-RestMethod -Uri $url -Headers @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } -Method Get
$ArtifactDownloadURL= $buildPipeline.resource.downloadUrl
$ArtifactDownloadURL = $ArtifactDownloadURL -replace "format=zip","format=file&subPath=%2Fautorest-modelerfour-$(artver).tgz"
echo "Raw Artifact Url: '$ArtifactDownloadURL'"
$downurl = (iwr "http://tinyurl.com/api-create.php?url=$( [System.Web.HttpUtility]::UrlEncode($ArtifactDownloadURL))" ).Content
echo "##vso[task.setvariable variable=DOWNURL]$downurl"
add `--use:` to the command line:
`autorest --use:$(DOWNURL) ` ...
<hr>
- task: GitHubComment@0
inputs:
gitHubConnection: "Azure"
repositoryName: "$(Build.Repository.Name)"
comment: |
You may test this build by running `autorest --reset` and then either:
<hr>
or use the following in your autorest configuration:
``` yaml
use-extension:
"@autorest/modelerfour": "$(DOWNURL)"
```
<hr>
add `--use:` to the command line:
If this build is good for you, give this comment a thumbs up. (👍)
`autorest --use:$(DOWNURL) ` ...
<hr>
And you should run `autorest --reset` again once you're finished testing to remove it.
or use the following in your autorest configuration:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.x'
``` yaml
use-extension:
"@autorest/modelerfour": "$(DOWNURL)"
```
<hr>
- script: npm install -g @autorest/compare@~0.3.0
displayName: Install autorest-compare
If this build is good for you, give this comment a thumbs up. (👍)
- script: autorest-compare --compare:.scripts/regression-compare.yaml --language:python
displayName: Regression Test - @autorest/python
And you should run `autorest --reset` again once you're finished testing to remove it.
- script: autorest-compare --compare:.scripts/regression-compare.yaml --language:typescript
displayName: Regression Test - @autorest/typescript
- task: UsePythonVersion@0
inputs:
versionSpec: "3.x"
- script: npm install -g @autorest/compare@~0.3.0
displayName: Install autorest-compare
- script: autorest-compare --compare:.scripts/regression-compare.yaml --language:python
displayName: Regression Test - @autorest/python
- script: autorest-compare --compare:.scripts/regression-compare.yaml --language:typescript
displayName: Regression Test - @autorest/typescript

Просмотреть файл

@ -1,25 +1,23 @@
var cp = require('child_process');
var cp = require("child_process");
require('./for-each').forEachProject((packageName, projectFolder, project) => {
require("./for-each").forEachProject((packageName, projectFolder, project) => {
if (project.scripts.watch) {
console.log(`npm run watch {cwd: ${__dirname}/../${projectFolder}}`);
const proc = cp.spawn('npm', ['run', 'watch'], { cwd: projectFolder, shell: true, stdio: "inherit" });
const proc = cp.spawn("npm", ["run", "watch"], { cwd: projectFolder, shell: true, stdio: "inherit" });
proc.on("error", (c, s) => {
console.log(packageName);
console.error(c);
console.error(s);
});
proc.on('exit', (c, s) => {
proc.on("exit", (c, s) => {
console.log(packageName);
console.error(c);
console.error(s);
});
proc.on('message', (c, s) => {
proc.on("message", (c, s) => {
console.log(packageName);
console.error(c);
console.error(s);
})
});
}
});

Просмотреть файл

@ -2,8 +2,8 @@
# configure plugins first
parser: "@typescript-eslint/parser"
plugins:
- "@typescript-eslint"
- "@typescript-eslint"
# then inherit the common settings
extends:
- "../.default-eslintrc.yaml"
- "../.default-eslintrc.yaml"

Просмотреть файл

@ -1,46 +1,70 @@
import { CodeModel, Schema, ObjectSchema, isObjectSchema, SchemaType, Property, ParameterLocation, Operation, Parameter, VirtualParameter, getAllProperties, ImplementationLocation, DictionarySchema } from '@azure-tools/codemodel';
import { Session } from '@azure-tools/autorest-extension-base';
import { values, items, length, Dictionary, refCount, clone } from '@azure-tools/linq';
import {
CodeModel,
Schema,
ObjectSchema,
isObjectSchema,
SchemaType,
Property,
ParameterLocation,
Operation,
Parameter,
VirtualParameter,
getAllProperties,
ImplementationLocation,
DictionarySchema,
} from "@azure-tools/codemodel";
import { Session } from "@azure-tools/autorest-extension-base";
import { values, items, length, Dictionary, refCount, clone } from "@azure-tools/linq";
export class Checker {
codeModel: CodeModel
codeModel: CodeModel;
options: Dictionary<any> = {};
constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename);
this.codeModel = session.model; // shadow(session.model, filename);
}
async init() {
// get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {});
this.options = await this.session.getValue("modelerfour", {});
return this;
}
checkOperationGroups() {
for (const dupe of values(this.codeModel.operationGroups).select(each => each.language.default.name).duplicates()) {
for (const dupe of values(this.codeModel.operationGroups)
.select((each) => each.language.default.name)
.duplicates()) {
this.session.error(`Duplicate Operation group '${dupe}' detected .`, []);
};
}
}
checkOperations() {
for (const group of this.codeModel.operationGroups) {
for (const dupe of values(group.operations).select(each => each.language.default.name).duplicates()) {
for (const dupe of values(group.operations)
.select((each) => each.language.default.name)
.duplicates()) {
this.session.error(`Duplicate Operation '${dupe}' detected.`, []);
};
}
}
}
checkSchemas() {
const allSchemas = values(<Dictionary<Schema[]>><any>this.codeModel.schemas).selectMany(schemas => Array.isArray(schemas) ? values(schemas) : []).toArray();
const allSchemas = values(<Dictionary<Schema[]>>(<any>this.codeModel.schemas))
.selectMany((schemas) => (Array.isArray(schemas) ? values(schemas) : []))
.toArray();
for (const each of values(allSchemas).where(each => !each.language.default.name)) {
for (const each of values(allSchemas).where((each) => !each.language.default.name)) {
this.session.warning(`Schema Missing Name '${JSON.stringify(each)}'.`, []);
}
const types = values(<Schema[]>this.codeModel.schemas.objects).concat(values(this.codeModel.schemas.groups)).concat(values(this.codeModel.schemas.choices)).concat(values(this.codeModel.schemas.sealedChoices)).toArray()
for (const dupe of values(types).duplicates(each => each.language.default.name)) {
const types = values(<Schema[]>this.codeModel.schemas.objects)
.concat(values(this.codeModel.schemas.groups))
.concat(values(this.codeModel.schemas.choices))
.concat(values(this.codeModel.schemas.sealedChoices))
.toArray();
for (const dupe of values(types).duplicates((each) => each.language.default.name)) {
this.session.error(`Duplicate object schemas with '${dupe.language.default.name}' name detected.`, []);
};
}
/* for (const dupe of values(this.codeModel.schemas.numbers).select(each => each.type).duplicates()) {
this.session.error(`Duplicate '${dupe}' detected.`, []);
@ -48,7 +72,7 @@ export class Checker {
}
process() {
if (this.options['additional-checks'] !== false) {
if (this.options["additional-checks"] !== false) {
this.checkOperationGroups();
this.checkOperations();
@ -57,4 +81,4 @@ export class Checker {
}
return this.codeModel;
}
}
}

Просмотреть файл

@ -3,17 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel';
import { Checker } from './checker';
import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Checker } from "./checker";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {});
const options = <any>await session.getValue("modelerfour", {});
// process
const plugin = await new Checker(session).init();
@ -22,19 +22,18 @@ export async function processRequest(host: Host) {
const result = plugin.process();
// throw on errors.
if (!await session.getValue('no-errors', false)) {
if (!(await session.getValue("no-errors", false))) {
session.checkpoint();
}
// output the model to the pipeline
if (options['emit-yaml-tags'] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4');
if (options["emit-yaml-tags"] !== false) {
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
}
if (options['emit-yaml-tags'] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags');
if (options["emit-yaml-tags"] !== true) {
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
}
} catch (E) {
if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);

Просмотреть файл

@ -1,11 +1,14 @@
#!/usr/bin/env node
// load modules from static linker filesystem.
try {
if (process.argv.indexOf('--no-static-loader') === -1 && process.env['no-static-loader'] === undefined && require('fs').existsSync(`${__dirname}/../dist/static-loader.js`)) {
if (
process.argv.indexOf("--no-static-loader") === -1 &&
process.env["no-static-loader"] === undefined &&
require("fs").existsSync(`${__dirname}/../dist/static-loader.js`)
) {
require(`${__dirname}/../dist/static-loader.js`).load(`${__dirname}/../dist/static_modules.fs`);
}
require(`${__dirname}/../dist/main.js`);
} catch (e) {
console.error(e);
}

Просмотреть файл

@ -1,15 +1,14 @@
import { CodeModel } from '@azure-tools/codemodel';
import { Session } from '@azure-tools/autorest-extension-base';
import { CodeModel } from "@azure-tools/codemodel";
import { Session } from "@azure-tools/autorest-extension-base";
export class Example {
codeModel: CodeModel
codeModel: CodeModel;
constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename);
this.codeModel = session.model; // shadow(session.model, filename);
}
process() {
return this.codeModel;
}
}
}

Просмотреть файл

@ -3,14 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel';
import { Example } from './example';
import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Example } from "./example";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
@ -22,9 +21,8 @@ export async function processRequest(host: Host) {
const result = plugin.process();
// output the model to the pipeline
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4');
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags');
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
} catch (E) {
if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);

Просмотреть файл

@ -1,31 +1,49 @@
import { CodeModel, Schema, ObjectSchema, isObjectSchema, SchemaType, Property, ParameterLocation, Operation, Parameter, VirtualParameter, getAllProperties, ImplementationLocation, Request } from '@azure-tools/codemodel';
import { Session } from '@azure-tools/autorest-extension-base';
import { values, items, length, Dictionary, refCount, clone } from '@azure-tools/linq';
import {
CodeModel,
Schema,
ObjectSchema,
isObjectSchema,
SchemaType,
Property,
ParameterLocation,
Operation,
Parameter,
VirtualParameter,
getAllProperties,
ImplementationLocation,
Request,
} from "@azure-tools/codemodel";
import { Session } from "@azure-tools/autorest-extension-base";
import { values, items, length, Dictionary, refCount, clone } from "@azure-tools/linq";
const xmsThreshold = 'x-ms-payload-flattening-threshold';
const xmsFlatten = 'x-ms-client-flatten';
const isCurrentlyFlattening = 'x-ms-flattening';
const hasBeenFlattened = 'x-ms-flattened';
const xmsThreshold = "x-ms-payload-flattening-threshold";
const xmsFlatten = "x-ms-client-flatten";
const isCurrentlyFlattening = "x-ms-flattening";
const hasBeenFlattened = "x-ms-flattened";
export class Flattener {
codeModel: CodeModel
codeModel: CodeModel;
options: Dictionary<any> = {};
threshold: number = 0;
recursePayload: boolean = false;
constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename);
this.codeModel = session.model; // shadow(session.model, filename);
}
async init() {
// get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {});
this.threshold = await this.session.getValue('payload-flattening-threshold', 0);
this.recursePayload = await this.session.getValue('recursive-payload-flattening', false);
this.options = await this.session.getValue("modelerfour", {});
this.threshold = await this.session.getValue("payload-flattening-threshold", 0);
this.recursePayload = await this.session.getValue("recursive-payload-flattening", false);
return this;
}
*getFlattenedParameters(parameter: Parameter, property: Property, path: Array<Property> = []): Iterable<VirtualParameter> {
*getFlattenedParameters(
parameter: Parameter,
property: Property,
path: Array<Property> = [],
): Iterable<VirtualParameter> {
if (property.readOnly) {
// skip read-only properties
return;
@ -35,29 +53,34 @@ export class Flattener {
yield* this.getFlattenedParameters(parameter, child, [...path, property]);
}
} else {
const vp = new VirtualParameter(property.language.default.name, property.language.default.description, property.schema, {
...property,
implementation: ImplementationLocation.Method,
originalParameter: parameter,
targetProperty: property,
pathToProperty: path
});
const vp = new VirtualParameter(
property.language.default.name,
property.language.default.description,
property.schema,
{
...property,
implementation: ImplementationLocation.Method,
originalParameter: parameter,
targetProperty: property,
pathToProperty: path,
},
);
delete (<any>vp).serializedName;
delete (<any>vp).readOnly;
delete (<any>vp).isDiscriminator;
delete (<any>vp).flattenedNames;
// if the parameter has "x-ms-parameter-grouping" extension, (and this is a top level parameter) then we should copy that to the vp.
if (path.length === 0 && parameter.extensions?.['x-ms-parameter-grouping']) {
(vp.extensions = vp.extensions || {})['x-ms-parameter-grouping'] = parameter.extensions?.['x-ms-parameter-grouping'];
if (path.length === 0 && parameter.extensions?.["x-ms-parameter-grouping"]) {
(vp.extensions = vp.extensions || {})["x-ms-parameter-grouping"] =
parameter.extensions?.["x-ms-parameter-grouping"];
}
yield vp;
}
// ·
}
/**
/**
* This flattens an request's parameters (ie, takes the parameters from an operation and if they are objects will attempt to create inline versions of them)
*/
flattenPayload(request: Request, parameter: Parameter, schema: ObjectSchema) {
@ -73,7 +96,6 @@ export class Flattener {
request.parameters?.push(vp);
}
}
}
/**
@ -90,14 +112,16 @@ export class Flattener {
if (state === true) {
// in progress.
throw new Error(`Circular reference encountered during processing of x-ms-client flatten ('${schema.language.default.name}')`);
throw new Error(
`Circular reference encountered during processing of x-ms-client flatten ('${schema.language.default.name}')`,
);
}
// hasn't started yet.
schema.extensions = schema.extensions || {};
schema.extensions[isCurrentlyFlattening] = true;
// ensure that parent schemas are done first -- this should remove
// ensure that parent schemas are done first -- this should remove
// the problem when the order isn't just right.
for (const parent of values(schema.parents?.immediate)) {
if (isObjectSchema(parent)) {
@ -107,7 +131,6 @@ export class Flattener {
if (schema.properties) {
for (const { key: index, value: property } of items(schema.properties).toArray().reverse()) {
if (isObjectSchema(property.schema) && property.extensions?.[xmsFlatten]) {
// first, ensure tha the child is pre-flattened
this.flattenSchema(property.schema);
@ -115,20 +138,30 @@ export class Flattener {
// remove that property from the scheama
schema.properties.splice(index, 1);
// copy all of the properties from the child into this
// schema
// copy all of the properties from the child into this
// schema
for (const childProperty of values(getAllProperties(property.schema))) {
schema.addProperty(new Property(childProperty.language.default.name, childProperty.language.default.description, childProperty.schema, {
...(<any>childProperty),
flattenedNames: [property.serializedName, ...childProperty.flattenedNames ? childProperty.flattenedNames : [childProperty.serializedName]],
required: property.required && childProperty.required
}));
schema.addProperty(
new Property(
childProperty.language.default.name,
childProperty.language.default.description,
childProperty.schema,
{
...(<any>childProperty),
flattenedNames: [
property.serializedName,
...(childProperty.flattenedNames ? childProperty.flattenedNames : [childProperty.serializedName]),
],
required: property.required && childProperty.required,
},
),
);
}
// remove the extension
delete property.extensions[xmsFlatten];
if (length(property.extensions) === 0) {
delete property['extensions'];
delete property["extensions"];
}
// and mark the child class as 'do-not-generate' ?
(property.schema.extensions = property.schema.extensions || {})[hasBeenFlattened] = true;
@ -143,18 +176,17 @@ export class Flattener {
// support 'x-ms-payload-flattening-threshold' per-operation
// support '--payload-flattening-threshold:X' global setting
if (this.options['flatten-models'] === true) {
if (this.options["flatten-models"] === true) {
for (const schema of values(this.codeModel.schemas.objects)) {
this.flattenSchema(schema);
}
if (!this.options['keep-unused-flattened-models']) {
if (!this.options["keep-unused-flattened-models"]) {
let dirty = false;
do {
// reset on every pass
dirty = false;
// remove unreferenced models
// remove unreferenced models
for (const { key, value: schema } of items(this.codeModel.schemas.objects).toArray()) {
// only remove unreferenced models that have been flattened.
if (!schema.extensions?.[hasBeenFlattened]) {
@ -162,12 +194,12 @@ export class Flattener {
}
if (schema.discriminatorValue || schema.discriminator) {
// it's polymorphic -- I don't think we can remove this
// it's polymorphic -- I don't think we can remove this
continue;
}
if (schema.children?.all || schema.parents?.all) {
// it's got either a parent or child schema.
// it's got either a parent or child schema.
continue;
}
@ -180,34 +212,33 @@ export class Flattener {
} while (dirty);
}
for (const schema of values(this.codeModel.schemas.objects)) {
if (schema.extensions) {
delete schema.extensions[isCurrentlyFlattening];
// don't want this until I have removed the unreferenced models.
// delete schema.extensions[hasBeenFlattened];
if (length(schema.extensions) === 0) {
delete schema['extensions'];
delete schema["extensions"];
}
}
}
}
if (this.options['flatten-payloads'] === true) {
if (this.options["flatten-payloads"] === true) {
/**
* BodyParameter Payload Flattening
*
* BodyParameter Payload Flattening
*
* A body parameter is flattened (one level) when:
*
*
* - the body parameter schema is an object
* - the body parameter schema is not polymorphic (is this true?)
*
*
*
*
*
*
* and one of:
* - the body parameter has x-ms-client-flatten: true
* - the operation has x-ms-payload-flattening-threshold greater than zero and the property count in the body parameter is lessthan or equal to that.
* - the global configuration option payload-flattening-threshold is greater than zero and the property count in the body parameter is lessthan or equal to that
*
*
*/
// flatten payloads
@ -216,12 +247,15 @@ export class Flattener {
for (const operation of group.operations) {
// when there are multiple requests in an operation
// and the generator asks not to flatten them
if (length(operation.requests) > 1 && this.options['multiple-request-parameter-flattening'] === false) {
if (length(operation.requests) > 1 && this.options["multiple-request-parameter-flattening"] === false) {
continue;
}
for (const request of values(operation.requests)) {
const body = values(request.parameters).first(p => p.protocol.http?.in === ParameterLocation.Body && p.implementation === ImplementationLocation.Method);
const body = values(request.parameters).first(
(p) =>
p.protocol.http?.in === ParameterLocation.Body && p.implementation === ImplementationLocation.Method,
);
if (body && isObjectSchema(body.schema)) {
const schema = <ObjectSchema>body.schema;
@ -236,12 +270,16 @@ export class Flattener {
continue;
}
if (!flattenOperationPayload) {
const threshold = <number>operation.extensions?.[xmsThreshold] ?? this.threshold;
if (threshold > 0) {
// get the count of the (non-readonly) properties in the schema
flattenOperationPayload = length(values(getAllProperties(schema)).where(property => property.readOnly !== true && property.schema.type !== SchemaType.Constant)) <= threshold;
flattenOperationPayload =
length(
values(getAllProperties(schema)).where(
(property) => property.readOnly !== true && property.schema.type !== SchemaType.Constant,
),
) <= threshold;
}
}

Просмотреть файл

@ -3,18 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel';
import { Flattener } from './flattener';
import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Flattener } from "./flattener";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {});
const options = <any>await session.getValue("modelerfour", {});
// process
const plugin = await new Flattener(session).init();
@ -23,14 +22,13 @@ export async function processRequest(host: Host) {
const result = plugin.process();
// output the model to the pipeline
if (options['emit-yaml-tags'] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4');
if (options["emit-yaml-tags"] !== false) {
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
}
if (options['emit-yaml-tags'] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags');
if (options["emit-yaml-tags"] !== true) {
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
}
} catch (E) {
if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);

Просмотреть файл

@ -1,32 +1,45 @@
import { CodeModel, Schema, GroupSchema, isObjectSchema, SchemaType, GroupProperty, ParameterLocation, Operation, Parameter, VirtualParameter, getAllProperties, ImplementationLocation, OperationGroup, Request, SchemaContext } from '@azure-tools/codemodel';
import { Session } from '@azure-tools/autorest-extension-base';
import { values, items, length, Dictionary, refCount, clone } from '@azure-tools/linq';
import { pascalCase, camelCase } from '@azure-tools/codegen';
const mergeReponseHeaders = 'merge-response-headers';
const enableParameterGrouping = 'group-parameters';
const xmsParameterGrouping = 'x-ms-parameter-grouping'
import {
CodeModel,
Schema,
GroupSchema,
isObjectSchema,
SchemaType,
GroupProperty,
ParameterLocation,
Operation,
Parameter,
VirtualParameter,
getAllProperties,
ImplementationLocation,
OperationGroup,
Request,
SchemaContext,
} from "@azure-tools/codemodel";
import { Session } from "@azure-tools/autorest-extension-base";
import { values, items, length, Dictionary, refCount, clone } from "@azure-tools/linq";
import { pascalCase, camelCase } from "@azure-tools/codegen";
const mergeReponseHeaders = "merge-response-headers";
const enableParameterGrouping = "group-parameters";
const xmsParameterGrouping = "x-ms-parameter-grouping";
export class Grouper {
codeModel: CodeModel
codeModel: CodeModel;
options: Dictionary<any> = {};
groups: Dictionary<GroupSchema> = {};
constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename);
this.codeModel = session.model; // shadow(session.model, filename);
}
async init() {
// get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {});
this.options = await this.session.getValue("modelerfour", {});
return this;
}
process() {
if (this.options[enableParameterGrouping] === true) {
for (const group of this.codeModel.operationGroups) {
for (const operation of group.operations) {
for (const request of values(operation.requests)) {
@ -53,17 +66,26 @@ export class Grouper {
proposedName(group: OperationGroup, operation: Operation, parameter: Parameter) {
const xmsp = parameter.extensions?.[xmsParameterGrouping];
if (xmsp.name && typeof xmsp.name === 'string') {
if (xmsp.name && typeof xmsp.name === "string") {
return xmsp.name;
}
const postfix = xmsp.postfix && typeof xmsp.postfix === 'string' ? xmsp.postfix : 'Parameters';
const postfix = xmsp.postfix && typeof xmsp.postfix === "string" ? xmsp.postfix : "Parameters";
return pascalCase(`${group.$key} ${operation.language.default.name} ${postfix}`);
}
processParameterGroup(group: OperationGroup, operation: Operation, request: Request) {
const grouped = [...values(operation.parameters).concat(values(request.parameters)).where(parameter => parameter.extensions?.[xmsParameterGrouping] && parameter.schema.type !== SchemaType.Constant && parameter.implementation !== ImplementationLocation.Client)];
const grouped = [
...values(operation.parameters)
.concat(values(request.parameters))
.where(
(parameter) =>
parameter.extensions?.[xmsParameterGrouping] &&
parameter.schema.type !== SchemaType.Constant &&
parameter.implementation !== ImplementationLocation.Client,
),
];
if (grouped.length > 0) {
// create a parameter group object schema for the selected parameters.
@ -75,33 +97,45 @@ export class Grouper {
// see if we've started the schema for this yet.
if (!this.groups[groupName]) {
// create a new object schema for this group
const schema = new GroupSchema(groupName, 'Parameter group');
const schema = new GroupSchema(groupName, "Parameter group");
schema.usage = [SchemaContext.Input];
this.groups[groupName] = schema;
this.codeModel.schemas.add(schema);
}
const schema = this.groups[groupName];
// see if the group has this parameter.
const existingProperty = values(schema.properties).first(each => each.language.default.name === parameter.language.default.name);
// see if the group has this parameter.
const existingProperty = values(schema.properties).first(
(each) => each.language.default.name === parameter.language.default.name,
);
if (existingProperty) {
// we have a property by this name one already
// mark the groupproperty with this parameter (so we can find it if needed)
existingProperty.originalParameter.push(parameter);
} else {
// create a property for this parameter.
const gp = new GroupProperty(parameter.language.default.name, parameter.language.default.description, parameter.schema, {
required: parameter.required,
});
const gp = new GroupProperty(
parameter.language.default.name,
parameter.language.default.description,
parameter.schema,
{
required: parameter.required,
},
);
gp.originalParameter.push(parameter);
schema.add(gp);
}
// check if this groupSchema has been added as a parameter for this operation yet.
if (!addedGroupedParameters.has(schema)) {
addedGroupedParameters.set(schema, request.addParameter(new Parameter(camelCase(schema.language.default.name), schema.language.default.description, schema, {
implementation: ImplementationLocation.Method,
})));
addedGroupedParameters.set(
schema,
request.addParameter(
new Parameter(camelCase(schema.language.default.name), schema.language.default.description, schema, {
implementation: ImplementationLocation.Method,
}),
),
);
}
// make sure that it's not optional if any parameter are not optional.
@ -115,7 +149,7 @@ export class Grouper {
if (parameter.extensions) {
delete parameter.extensions[xmsParameterGrouping];
if (length(parameter.extensions) === 0) {
delete parameter['extensions'];
delete parameter["extensions"];
}
}
}
@ -123,6 +157,6 @@ export class Grouper {
}
processResponseHeaders(operation: Operation) {
throw new Error('Method not implemented.');
throw new Error("Method not implemented.");
}
}
}

Просмотреть файл

@ -3,18 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel';
import { Grouper } from './grouper';
import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Grouper } from "./grouper";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {});
const options = <any>await session.getValue("modelerfour", {});
// process
const plugin = await new Grouper(session).init();
@ -23,14 +22,13 @@ export async function processRequest(host: Host) {
const result = plugin.process();
// output the model to the pipeline
if (options['emit-yaml-tags'] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4');
if (options["emit-yaml-tags"] !== false) {
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
}
if (options['emit-yaml-tags'] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags');
if (options["emit-yaml-tags"] !== true) {
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
}
} catch (E) {
if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);

Просмотреть файл

@ -1,18 +1,18 @@
import { AutoRestExtension, } from '@azure-tools/autorest-extension-base';
import { processRequest as modelerfour } from './modeler/plugin-modelerfour';
import { processRequest as preNamer } from './prenamer/plugin-prenamer';
import { processRequest as flattener } from './flattener/plugin-flattener';
import { processRequest as grouper } from './grouper/plugin-grouper';
import { processRequest as checker } from './checker/plugin-checker';
import { processRequest as prechecker } from './quality-precheck/prechecker';
import { AutoRestExtension } from "@azure-tools/autorest-extension-base";
import { processRequest as modelerfour } from "./modeler/plugin-modelerfour";
import { processRequest as preNamer } from "./prenamer/plugin-prenamer";
import { processRequest as flattener } from "./flattener/plugin-flattener";
import { processRequest as grouper } from "./grouper/plugin-grouper";
import { processRequest as checker } from "./checker/plugin-checker";
import { processRequest as prechecker } from "./quality-precheck/prechecker";
export async function initializePlugins(pluginHost: AutoRestExtension) {
pluginHost.Add('prechecker', prechecker);
pluginHost.Add('modelerfour', modelerfour);
pluginHost.Add('grouper', grouper);
pluginHost.Add('pre-namer', preNamer);
pluginHost.Add('flattener', flattener);
pluginHost.Add('checker', checker);
pluginHost.Add("prechecker", prechecker);
pluginHost.Add("modelerfour", modelerfour);
pluginHost.Add("grouper", grouper);
pluginHost.Add("pre-namer", preNamer);
pluginHost.Add("flattener", flattener);
pluginHost.Add("checker", checker);
}
async function main() {
@ -21,4 +21,4 @@ async function main() {
await pluginHost.Run();
}
main();
main();

Просмотреть файл

@ -1,9 +1,17 @@
import { Session } from '@azure-tools/autorest-extension-base';
import * as OpenAPI from '@azure-tools/openapi';
import { values, length, items, ToDictionary, Dictionary } from '@azure-tools/linq';
import { ChoiceSchema, XmlSerlializationFormat, ExternalDocumentation, ApiVersion, Deprecation, ChoiceValue, SetType } from '@azure-tools/codemodel';
import { StringFormat, JsonType, ParameterLocation } from '@azure-tools/openapi';
import { getPascalIdentifier } from '@azure-tools/codegen';
import { Session } from "@azure-tools/autorest-extension-base";
import * as OpenAPI from "@azure-tools/openapi";
import { values, length, items, ToDictionary, Dictionary } from "@azure-tools/linq";
import {
ChoiceSchema,
XmlSerlializationFormat,
ExternalDocumentation,
ApiVersion,
Deprecation,
ChoiceValue,
SetType,
} from "@azure-tools/codemodel";
import { StringFormat, JsonType, ParameterLocation } from "@azure-tools/openapi";
import { getPascalIdentifier } from "@azure-tools/codegen";
export interface XMSEnum {
modelAsString?: boolean;
@ -11,67 +19,66 @@ export interface XMSEnum {
name: string;
}
const removeKnownParameters = [
'x-ms-metadata',
'x-ms-enum',
'x-ms-code-generation-settings',
'x-ms-client-name',
'x-ms-parameter-location',
'x-ms-original',
'x-ms-requestBody-name',
'x-ms-requestBody-index',
'x-ms-api-version',
'x-ms-text'
"x-ms-metadata",
"x-ms-enum",
"x-ms-code-generation-settings",
"x-ms-client-name",
"x-ms-parameter-location",
"x-ms-original",
"x-ms-requestBody-name",
"x-ms-requestBody-index",
"x-ms-api-version",
"x-ms-text",
];
// ref: https://www.w3schools.com/charsets/ref_html_ascii.asp
const specialCharacterMapping: { [character: string]: string } = {
'!': 'exclamation mark',
'"': 'quotation mark',
'#': 'number sign',
'$': 'dollar sign',
'%': 'percent sign',
'&': 'ampersand',
'\'': 'apostrophe',
'(': 'left parenthesis',
')': 'right parenthesis',
'*': 'asterisk',
'+': 'plus sign',
',': 'comma',
'-': 'hyphen',
'.': 'period',
'/': 'slash',
':': 'colon',
';': 'semicolon',
'<': 'less-than',
'=': 'equals-to',
'>': 'greater-than',
'?': 'question mark',
'@': 'at sign',
'[': 'left square bracket',
'\\': 'backslash',
']': 'right square bracket',
'^': 'caret',
'_': 'underscore',
'`': 'grave accent',
'{': 'left curly brace',
'|': 'vertical bar',
'}': 'right curly brace',
'~': 'tilde'
"!": "exclamation mark",
'"': "quotation mark",
"#": "number sign",
"$": "dollar sign",
"%": "percent sign",
"&": "ampersand",
"'": "apostrophe",
"(": "left parenthesis",
")": "right parenthesis",
"*": "asterisk",
"+": "plus sign",
",": "comma",
"-": "hyphen",
".": "period",
"/": "slash",
":": "colon",
";": "semicolon",
"<": "less-than",
"=": "equals-to",
">": "greater-than",
"?": "question mark",
"@": "at sign",
"[": "left square bracket",
"\\": "backslash",
"]": "right square bracket",
"^": "caret",
"_": "underscore",
"`": "grave accent",
"{": "left curly brace",
"|": "vertical bar",
"}": "right curly brace",
"~": "tilde",
};
const apiVersionParameterNames = [
'api-version',
'apiversion',
'x-ms-api-version',
'x-ms-version'
];
const apiVersionParameterNames = ["api-version", "apiversion", "x-ms-api-version", "x-ms-version"];
export function getValidEnumValueName(originalString: string): string {
if (typeof originalString === 'string') {
return !originalString.match(/[A-Za-z0-9]/g) ?
getPascalIdentifier(originalString.split('').map(x => specialCharacterMapping[x]).join(' '))
if (typeof originalString === "string") {
return !originalString.match(/[A-Za-z0-9]/g)
? getPascalIdentifier(
originalString
.split("")
.map((x) => specialCharacterMapping[x])
.join(" "),
)
: originalString;
}
return originalString;
@ -79,11 +86,10 @@ export function getValidEnumValueName(originalString: string): string {
export class Interpretations {
isTrue(value: any) {
return (value === true || value === 'true' || value === 'True' || value === 'TRUE');
return value === true || value === "true" || value === "True" || value === "TRUE";
}
getConstantValue(schema: OpenAPI.Schema, value: any) {
switch (schema.type) {
case JsonType.String:
switch (schema.format) {
@ -125,7 +131,7 @@ export class Interpretations {
return value;
case StringFormat.Password:
throw new Error('Constant values for String/Passwords should never be in input documents');
throw new Error("Constant values for String/Passwords should never be in input documents");
case StringFormat.OData:
return value;
@ -137,7 +143,9 @@ export class Interpretations {
default:
// console.error(`String schema '${name}' with unknown format: '${schema.format}' is treated as simple string.`);
throw new Error(`Unknown type for constant value for String '${schema.format}'--cannot create constant value.`);
throw new Error(
`Unknown type for constant value for String '${schema.format}'--cannot create constant value.`,
);
}
case JsonType.Boolean:
@ -153,41 +161,48 @@ export class Interpretations {
isApiVersionParameter(parameter: OpenAPI.Parameter): boolean {
// Always let x-ms-api-version override the check
if (parameter['x-ms-api-version'] !== undefined) {
return !!parameter['x-ms-api-version'] === true;
if (parameter["x-ms-api-version"] !== undefined) {
return !!parameter["x-ms-api-version"] === true;
}
// It's an api-version parameter if it's a query param with an expected name
return parameter.in === ParameterLocation.Query && !!apiVersionParameterNames.find(each => each === parameter.name.toLowerCase());
return (
parameter.in === ParameterLocation.Query &&
!!apiVersionParameterNames.find((each) => each === parameter.name.toLowerCase())
);
}
getEnumChoices(schema: OpenAPI.Schema): Array<ChoiceValue> {
if (schema && schema.enum) {
const xmse = <XMSEnum>schema['x-ms-enum'];
const xmse = <XMSEnum>schema["x-ms-enum"];
return xmse && xmse.values ?
xmse.values.map((each) => {
const name = getValidEnumValueName((each.name !== undefined) ? each.name : each.value);
const value = this.getConstantValue(schema, each.value);
return new ChoiceValue(`${name}`, each.description || ``, value);
}) :
schema.enum.map(each => {
const name = getValidEnumValueName(each);
const value = this.getConstantValue(schema, each);
return new ChoiceValue(`${name}`, ``, value);
});
return xmse && xmse.values
? xmse.values.map((each) => {
const name = getValidEnumValueName(each.name !== undefined ? each.name : each.value);
const value = this.getConstantValue(schema, each.value);
return new ChoiceValue(`${name}`, each.description || ``, value);
})
: schema.enum.map((each) => {
const name = getValidEnumValueName(each);
const value = this.getConstantValue(schema, each);
return new ChoiceValue(`${name}`, ``, value);
});
}
return [];
}
isEmptyObject(schema: OpenAPI.Schema): boolean {
return (schema.type === JsonType.Object && length(schema.allOf) + length(schema.anyOf) + length(schema.oneOf) + length(schema.properties) === 0 && !schema.discriminator);
return (
schema.type === JsonType.Object &&
length(schema.allOf) + length(schema.anyOf) + length(schema.oneOf) + length(schema.properties) === 0 &&
!schema.discriminator
);
}
getSerialization(schema: OpenAPI.Schema): any | undefined {
const xml = this.getXmlSerialization(schema);
if (xml) {
return {
xml
xml,
};
}
return undefined;
@ -195,18 +210,18 @@ export class Interpretations {
getXmlSerialization(schema: OpenAPI.Schema): XmlSerlializationFormat | undefined {
if (schema.xml) {
if (schema.xml['x-ms-text'] && schema.xml.attribute) {
if (schema.xml["x-ms-text"] && schema.xml.attribute) {
throw new Error(`XML serialization for a schema cannot be in both 'text' and 'attribute'`);
}
return {
attribute: schema.xml.attribute || false,
wrapped: schema.xml.wrapped || false,
text: schema.xml['x-ms-text'] || false,
text: schema.xml["x-ms-text"] || false,
name: schema.xml.name || undefined,
namespace: schema.xml.namespace || undefined,
prefix: schema.xml.prefix || undefined,
extensions: this.getExtensionProperties(schema.xml)
extensions: this.getExtensionProperties(schema.xml),
};
}
return undefined;
@ -218,18 +233,24 @@ export class Interpretations {
return undefined;
}
getApiVersions(schema: OpenAPI.Schema | OpenAPI.HttpOperation | OpenAPI.PathItem): Array<ApiVersion> | undefined {
if (schema['x-ms-metadata'] && schema['x-ms-metadata']['apiVersions']) {
const v = values(<Array<string>>schema['x-ms-metadata']['apiVersions']).select(each => SetType(ApiVersion, {
version: each.replace(/^-/, '').replace(/\+$/, ''),
range: each.startsWith('-') ? <any>'-' : each.endsWith('+') ? '+' : undefined
})).toArray();
if (schema["x-ms-metadata"] && schema["x-ms-metadata"]["apiVersions"]) {
const v = values(<Array<string>>schema["x-ms-metadata"]["apiVersions"])
.select((each) =>
SetType(ApiVersion, {
version: each.replace(/^-/, "").replace(/\+$/, ""),
range: each.startsWith("-") ? <any>"-" : each.endsWith("+") ? "+" : undefined,
}),
)
.toArray();
return v;
}
return undefined;
}
getApiVersionValues(node: OpenAPI.Schema | OpenAPI.HttpOperation | OpenAPI.PathItem): Array<string> {
if (node['x-ms-metadata'] && node['x-ms-metadata']['apiVersions']) {
return values(<Array<string>>node['x-ms-metadata']['apiVersions']).distinct().toArray();
if (node["x-ms-metadata"] && node["x-ms-metadata"]["apiVersions"]) {
return values(<Array<string>>node["x-ms-metadata"]["apiVersions"])
.distinct()
.toArray();
}
return [];
}
@ -240,32 +261,36 @@ export class Interpretations {
return undefined;
}
constructor(private session: Session<OpenAPI.Model>) {
}
constructor(private session: Session<OpenAPI.Model>) {}
xmsMeta(obj: any, key: string) {
const m = obj['x-ms-metadata'];
const m = obj["x-ms-metadata"];
return m ? m[key] : undefined;
}
xmsMetaFallback(obj: any, obj2: any, key: string) {
return this.xmsMeta(obj, key) || this.xmsMeta(obj2, key)
return this.xmsMeta(obj, key) || this.xmsMeta(obj2, key);
}
splitOpId(opId: string) {
const p = opId.indexOf('_');
const p = opId.indexOf("_");
return p != -1 ? {
group: opId.substr(0, p),
member: opId.substr(p + 1)
} : {
group: '',
member: opId
};
return p != -1
? {
group: opId.substr(0, p),
member: opId.substr(p + 1),
}
: {
group: "",
member: opId,
};
}
isBinarySchema(schema: OpenAPI.Schema | undefined) {
return !!(schema && (schema.format === StringFormat.Binary || schema.format === 'file' || <any>schema.type === 'file'));
return !!(
schema &&
(schema.format === StringFormat.Binary || schema.format === "file" || <any>schema.type === "file")
);
}
getOperationId(httpMethod: string, path: string, original: OpenAPI.HttpOperation) {
@ -275,24 +300,37 @@ export class Interpretations {
// synthesize from tags.
if (original.tags && length(original.tags) > 0) {
switch (length(original.tags)) {
case 0:
break;
case 1:
this.session.warning(`Generating 'operationId' for '${httpMethod}' operation on path '${path}' `, ['Interpretations'], original);
this.session.warning(
`Generating 'operationId' for '${httpMethod}' operation on path '${path}' `,
["Interpretations"],
original,
);
return this.splitOpId(`${original.tags[0]}`);
}
this.session.warning(`Generating 'operationId' for '${httpMethod}' operation on path '${path}' `, ['Interpretations'], original);
this.session.warning(
`Generating 'operationId' for '${httpMethod}' operation on path '${path}' `,
["Interpretations"],
original,
);
return this.splitOpId(`${original.tags[0]}_${original.tags[1]}`);
}
this.session.error(`NEED 'operationId' for '${httpMethod}' operation on path '${path}' `, ['Interpretations'], original);
this.session.error(
`NEED 'operationId' for '${httpMethod}' operation on path '${path}' `,
["Interpretations"],
original,
);
return this.splitOpId('unknown-method');
return this.splitOpId("unknown-method");
}
getDescription(defaultValue: string, original: OpenAPI.Extensions & { title?: string; summary?: string; description?: string }): string {
getDescription(
defaultValue: string,
original: OpenAPI.Extensions & { title?: string; summary?: string; description?: string },
): string {
if (original) {
return original.description || original.title || original.summary || defaultValue;
}
@ -300,20 +338,25 @@ export class Interpretations {
}
getPreferredName(original: any, preferredName?: string, fallbackName?: string) {
return original['x-ms-client-name'] ?? preferredName ?? original?.['x-ms-metadata']?.['name'] ?? fallbackName ?? original['name'] ?? 'MISSING_NAME';
return (
original["x-ms-client-name"] ??
preferredName ??
original?.["x-ms-metadata"]?.["name"] ??
fallbackName ??
original["name"] ??
"MISSING_NAME"
);
}
getName(defaultValue: string, original: any): string {
return original['x-ms-client-name'] ?? original?.['x-ms-metadata']?.['name'] ?? defaultValue;
return original["x-ms-client-name"] ?? original?.["x-ms-metadata"]?.["name"] ?? defaultValue;
}
/** gets the operation path from metadata, falls back to the OAI3 path key */
getPath(pathItem: OpenAPI.PathItem, operation: OpenAPI.HttpOperation, path: string) {
return this.xmsMeta(pathItem, 'path') || this.xmsMeta(operation, 'path') || path;
return this.xmsMeta(pathItem, "path") || this.xmsMeta(operation, "path") || path;
}
/*
/** creates server entries that are kept in the codeModel.protocol.http, and then referenced in each operation
*
@ -358,7 +401,7 @@ export class Interpretations {
*/
getEnumSchemaForVarible(name: string, somethingWithEnum: { enum?: Array<string> }): ChoiceSchema {
return new ChoiceSchema(name, this.getDescription('MISSING-SERVER-VARIABLE-ENUM-DESCRIPTION', somethingWithEnum));
return new ChoiceSchema(name, this.getDescription("MISSING-SERVER-VARIABLE-ENUM-DESCRIPTION", somethingWithEnum));
}
getExtensionProperties(dictionary: Dictionary<any>, additional?: Dictionary<any>): Dictionary<any> | undefined {
@ -372,11 +415,11 @@ export class Interpretations {
return main;
}
getClientDefault(dictionary: Dictionary<any>, additional?: Dictionary<any>): string | number | boolean | undefined {
return dictionary?.['x-ms-client-default'] || additional?.['x-ms-client-default'] || undefined;
return dictionary?.["x-ms-client-default"] || additional?.["x-ms-client-default"] || undefined;
}
static getExtensionProperties(dictionary: Dictionary<any>): Dictionary<any> | undefined {
const result = ToDictionary(OpenAPI.includeXDash(dictionary), each => dictionary[each]);
const result = ToDictionary(OpenAPI.includeXDash(dictionary), (each) => dictionary[each]);
for (const each of removeKnownParameters) {
delete result[each];
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -3,19 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { deserialize, serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import * as OpenAPI from '@azure-tools/openapi';
import { ModelerFour } from './modelerfour';
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel';
import { deserialize, serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import * as OpenAPI from "@azure-tools/openapi";
import { ModelerFour } from "./modelerfour";
import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<OpenAPI.Model>(host, undefined, undefined, 'prechecked-openapi-document');
const options = <any>await session.getValue('modelerfour', {});
const session = await startSession<OpenAPI.Model>(host, undefined, undefined, "prechecked-openapi-document");
const options = <any>await session.getValue("modelerfour", {});
// process
const modeler = await new ModelerFour(session).init();
@ -24,17 +23,16 @@ export async function processRequest(host: Host) {
const codeModel = modeler.process();
// throw on errors.
if (!await session.getValue('ignore-errors', false)) {
if (!(await session.getValue("ignore-errors", false))) {
session.checkpoint();
}
// output the model to the pipeline
if (options['emit-yaml-tags'] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(codeModel, codeModelSchema), undefined, 'code-model-v4');
if (options["emit-yaml-tags"] !== false) {
host.WriteFile("code-model-v4.yaml", serialize(codeModel, codeModelSchema), undefined, "code-model-v4");
}
if (options['emit-yaml-tags'] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(codeModel), undefined, 'code-model-v4-no-tags');
if (options["emit-yaml-tags"] !== true) {
host.WriteFile("code-model-v4-no-tags.yaml", serialize(codeModel), undefined, "code-model-v4-no-tags");
}
} catch (E) {
if (debug) {

Просмотреть файл

@ -3,17 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel';
import { PreNamer } from './prenamer';
import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { PreNamer } from "./prenamer";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {});
const options = <any>await session.getValue("modelerfour", {});
// process
const plugin = await new PreNamer(session).init();
@ -22,11 +22,11 @@ export async function processRequest(host: Host) {
const result = plugin.process();
// output the model to the pipeline
if (options['emit-yaml-tags'] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4');
if (options["emit-yaml-tags"] !== false) {
host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
}
if (options['emit-yaml-tags'] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags');
if (options["emit-yaml-tags"] !== true) {
host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
}
} catch (E) {
if (debug) {

Просмотреть файл

@ -1,7 +1,32 @@
import { CodeModel, Parameter, isVirtualParameter, ObjectSchema, isObjectSchema, getAllParentProperties, Languages, SchemaType, Schema, ChoiceSchema, SealedChoiceSchema, GroupSchema, ImplementationLocation, Operation, Request, Response } from '@azure-tools/codemodel';
import { Session } from '@azure-tools/autorest-extension-base';
import { values, length, Dictionary, when, items } from '@azure-tools/linq';
import { removeSequentialDuplicates, fixLeadingNumber, deconstruct, selectName, Style, Styler, pascalCase } from '@azure-tools/codegen';
import {
CodeModel,
Parameter,
isVirtualParameter,
ObjectSchema,
isObjectSchema,
getAllParentProperties,
Languages,
SchemaType,
Schema,
ChoiceSchema,
SealedChoiceSchema,
GroupSchema,
ImplementationLocation,
Operation,
Request,
Response,
} from "@azure-tools/codemodel";
import { Session } from "@azure-tools/autorest-extension-base";
import { values, length, Dictionary, when, items } from "@azure-tools/linq";
import {
removeSequentialDuplicates,
fixLeadingNumber,
deconstruct,
selectName,
Style,
Styler,
pascalCase,
} from "@azure-tools/codegen";
function getNameOptions(typeName: string, components: Array<string>) {
const result = new Set<string>();
@ -13,37 +38,61 @@ function getNameOptions(typeName: string, components: Array<string>) {
}
// add a second-to-last-ditch option as <typename>.<name>
result.add(Style.pascal([...removeSequentialDuplicates([...fixLeadingNumber(deconstruct(typeName)), ...deconstruct(components.last)])]));
result.add(
Style.pascal([
...removeSequentialDuplicates([...fixLeadingNumber(deconstruct(typeName)), ...deconstruct(components.last)]),
]),
);
return [...result.values()];
}
function isUnassigned(value: string) {
return !value || (value.indexOf('·') > -1);
return !value || value.indexOf("·") > -1;
}
interface SetNameOptions {
removeDuplicates: boolean
};
removeDuplicates: boolean;
}
function setName(thing: { language: Languages }, styler: Styler, defaultValue: string, overrides: Dictionary<string>, options?: SetNameOptions) {
function setName(
thing: { language: Languages },
styler: Styler,
defaultValue: string,
overrides: Dictionary<string>,
options?: SetNameOptions,
) {
options = {
removeDuplicates: true,
...options
...options,
};
thing.language.default.name = styler(defaultValue && isUnassigned(thing.language.default.name) ? defaultValue : thing.language.default.name, options.removeDuplicates, overrides);
thing.language.default.name = styler(
defaultValue && isUnassigned(thing.language.default.name) ? defaultValue : thing.language.default.name,
options.removeDuplicates,
overrides,
);
if (!thing.language.default.name) {
throw new Error('Name is empty!');
throw new Error("Name is empty!");
}
}
function setNameAllowEmpty(thing: { language: Languages }, styler: Styler, defaultValue: string, overrides: Dictionary<string>, options?: SetNameOptions) {
function setNameAllowEmpty(
thing: { language: Languages },
styler: Styler,
defaultValue: string,
overrides: Dictionary<string>,
options?: SetNameOptions,
) {
options = {
removeDuplicates: true,
...options
...options,
};
thing.language.default.name = styler(defaultValue && isUnassigned(thing.language.default.name) ? defaultValue : thing.language.default.name, options.removeDuplicates, overrides);
thing.language.default.name = styler(
defaultValue && isUnassigned(thing.language.default.name) ? defaultValue : thing.language.default.name,
options.removeDuplicates,
overrides,
);
}
/*
@ -57,14 +106,13 @@ function deduplicateSchemaName(
schema: Schema,
schemaNames: Set<string>,
session: Session<CodeModel>,
indexer: (schema: Schema, newName: string) => string =
(schema: Schema, proposedName: string) => proposedName
indexer: (schema: Schema, newName: string) => string = (schema: Schema, proposedName: string) => proposedName,
): void {
const schemaName = schema.language.default.name;
const maxDedupes = 1000;
if (schemaNames.has(indexer(schema, schemaName))) {
for (let i = 1; i <= maxDedupes; i++) {
const newName = `${schemaName}AutoGenerated${(i === 1 ? "" : i)}`;
const newName = `${schemaName}AutoGenerated${i === 1 ? "" : i}`;
if (!schemaNames.has(indexer(schema, newName))) {
schema.language.default.name = newName;
schemaNames.add(indexer(schema, newName));
@ -73,7 +121,10 @@ function deduplicateSchemaName(
}
}
session.error(`Attempted to deduplicate schema name '${schema.language.default.name}' more than ${maxDedupes} times and failed.`, ["PreNamer/DeduplicateName"])
session.error(
`Attempted to deduplicate schema name '${schema.language.default.name}' more than ${maxDedupes} times and failed.`,
["PreNamer/DeduplicateName"],
);
}
// We haven't seen the name before, add it
@ -81,7 +132,7 @@ function deduplicateSchemaName(
}
export class PreNamer {
codeModel: CodeModel
codeModel: CodeModel;
options: Dictionary<any> = {};
format = {
parameter: Style.camel,
@ -97,20 +148,20 @@ export class PreNamer {
client: Style.pascal,
local: Style.camel,
global: Style.pascal,
override: <Dictionary<string>>{}
}
override: <Dictionary<string>>{},
};
enum = 0;
constant = 0;
constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename);
this.codeModel = session.model; // shadow(session.model, filename);
}
async init() {
// get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {});
this.options = await this.session.getValue("modelerfour", {});
const naming = this.options.naming || {};
const maxPreserve = Number(naming['preserve-uppercase-max-length']) || 3;
const maxPreserve = Number(naming["preserve-uppercase-max-length"]) || 3;
this.format = {
parameter: Style.select(naming.parameter, Style.camel, maxPreserve),
property: Style.select(naming.property, Style.camel, maxPreserve),
@ -125,23 +176,22 @@ export class PreNamer {
type: Style.select(naming.type, Style.pascal, maxPreserve),
local: Style.select(naming.local, Style.camel, maxPreserve),
global: Style.select(naming.global, Style.pascal, maxPreserve),
override: naming.override || {}
}
override: naming.override || {},
};
return this;
}
isUnassigned(value: string) {
return !value || (value.indexOf('·') > -1);
return !value || value.indexOf("·") > -1;
}
process() {
if (this.options['prenamer'] === false) {
if (this.options["prenamer"] === false) {
return this.codeModel;
}
const deduplicateSchemaNames =
!!this.options['lenient-model-deduplication'] ||
!!this.options['resolve-schema-name-collisons'];
!!this.options["lenient-model-deduplication"] || !!this.options["resolve-schema-name-collisons"];
// choice
const choiceSchemaNames = new Set<string>();
@ -153,7 +203,7 @@ export class PreNamer {
}
for (const choice of values(schema.choices)) {
setName(choice, this.format.choiceValue, '', this.format.override, { removeDuplicates: false });
setName(choice, this.format.choiceValue, "", this.format.override, { removeDuplicates: false });
}
}
@ -167,7 +217,7 @@ export class PreNamer {
}
for (const choice of values(schema.choices)) {
setName(choice, this.format.choiceValue, '', this.format.override, { removeDuplicates: false });
setName(choice, this.format.choiceValue, "", this.format.override, { removeDuplicates: false });
}
}
@ -207,7 +257,7 @@ export class PreNamer {
setName(schema, this.format.type, schema.type, this.format.override);
if (isUnassigned(schema.language.default.description)) {
schema.language.default.description = 'date in seconds since 1970-01-01T00:00:00Z.';
schema.language.default.description = "date in seconds since 1970-01-01T00:00:00Z.";
}
}
@ -229,7 +279,12 @@ export class PreNamer {
// dictionary
for (const schema of values(this.codeModel.schemas.dictionaries)) {
setName(schema, this.format.type, `DictionaryOf${schema.elementType.language.default.name}`, this.format.override);
setName(
schema,
this.format.type,
`DictionaryOf${schema.elementType.language.default.name}`,
this.format.override,
);
if (isUnassigned(schema.language.default.description)) {
schema.language.default.description = `Dictionary of ${schema.elementType.language.default.name}`;
}
@ -244,52 +299,54 @@ export class PreNamer {
const objectSchemaNames = new Set<string>();
for (const schema of values(this.codeModel.schemas.objects)) {
setName(schema, this.format.type, '', this.format.override);
setName(schema, this.format.type, "", this.format.override);
if (deduplicateSchemaNames) {
deduplicateSchemaName(
schema,
objectSchemaNames,
this.session,
(schema: Schema, proposedName: string) =>
`${(schema.language.default.namespace || "")}.${proposedName}`);
(schema: Schema, proposedName: string) => `${schema.language.default.namespace || ""}.${proposedName}`,
);
}
for (const property of values(schema.properties)) {
setName(property, this.format.property, '', this.format.override);
setName(property, this.format.property, "", this.format.override);
}
}
const groupSchemaNames = new Set<string>();
for (const schema of values(this.codeModel.schemas.groups)) {
setName(schema, this.format.type, '', this.format.override);
setName(schema, this.format.type, "", this.format.override);
if (deduplicateSchemaNames) {
deduplicateSchemaName(
schema,
groupSchemaNames,
this.session,
(schema: Schema, proposedName: string) =>
`${(schema.language.default.namespace || "")}.${proposedName}`);
(schema: Schema, proposedName: string) => `${schema.language.default.namespace || ""}.${proposedName}`,
);
}
for (const property of values(schema.properties)) {
setName(property, this.format.property, '', this.format.override);
setName(property, this.format.property, "", this.format.override);
}
}
for (const parameter of values(this.codeModel.globalParameters)) {
if (parameter.schema.type === SchemaType.Constant) {
setName(parameter, this.format.constantParameter, '', this.format.override);
setName(parameter, this.format.constantParameter, "", this.format.override);
} else {
setName(parameter, this.format.parameter, '', this.format.override);
setName(parameter, this.format.parameter, "", this.format.override);
}
}
for (const operationGroup of this.codeModel.operationGroups) {
setNameAllowEmpty(operationGroup, this.format.operationGroup, operationGroup.$key, this.format.override, { removeDuplicates: false });
setNameAllowEmpty(operationGroup, this.format.operationGroup, operationGroup.$key, this.format.override, {
removeDuplicates: false,
});
for (const operation of operationGroup.operations) {
setName(operation, this.format.operation, '', this.format.override);
setName(operation, this.format.operation, "", this.format.override);
this.setParameterNames(operation);
for (const request of values(operation.requests)) {
@ -326,23 +383,20 @@ export class PreNamer {
private setParameterNames(parameterContainer: Operation | Request) {
for (const parameter of values(parameterContainer.signatureParameters)) {
if (parameter.schema.type === SchemaType.Constant) {
setName(parameter, this.format.constantParameter, '', this.format.override);
}
else {
setName(parameter, this.format.parameter, '', this.format.override);
setName(parameter, this.format.constantParameter, "", this.format.override);
} else {
setName(parameter, this.format.parameter, "", this.format.override);
}
}
for (const parameter of values(parameterContainer.parameters)) {
if ((parameterContainer.signatureParameters ?? []).indexOf(parameter) === -1) {
if (parameter.schema.type === SchemaType.Constant) {
setName(parameter, this.format.constantParameter, '', this.format.override);
}
else {
setName(parameter, this.format.constantParameter, "", this.format.override);
} else {
if (parameter.implementation === ImplementationLocation.Client) {
setName(parameter, this.format.global, '', this.format.override);
}
else {
setName(parameter, this.format.local, '', this.format.override);
setName(parameter, this.format.global, "", this.format.override);
} else {
setName(parameter, this.format.local, "", this.format.override);
}
}
}
@ -352,13 +406,13 @@ export class PreNamer {
private setResponseHeaderNames(response: Response) {
if (response.protocol.http) {
for (const { value: header } of items(response.protocol.http.headers)) {
setName(header as {language: Languages}, this.format.responseHeader, '', this.format.override);
setName(header as { language: Languages }, this.format.responseHeader, "", this.format.override);
}
}
}
fixParameterCollisions() {
for (const operation of values(this.codeModel.operationGroups).selectMany(each => each.operations)) {
for (const operation of values(this.codeModel.operationGroups).selectMany((each) => each.operations)) {
for (const request of values(operation.requests)) {
const parameters = values(operation.signatureParameters).concat(values(request.signatureParameters));
@ -380,21 +434,22 @@ export class PreNamer {
for (const parameter of collisions) {
let options = [parameter.language.default.name];
if (isVirtualParameter(parameter)) {
options = getNameOptions(parameter.schema.language.default.name, [parameter.language.default.name, ...parameter.pathToProperty.map(each => each.language.default.name)]).map(each => this.format.parameter(each));
options = getNameOptions(parameter.schema.language.default.name, [
parameter.language.default.name,
...parameter.pathToProperty.map((each) => each.language.default.name),
]).map((each) => this.format.parameter(each));
}
parameter.language.default.name = this.format.parameter(selectName(options, usedNames));
}
}
}
}
fixCollisions(schema: ObjectSchema) {
for (const each of values(schema.parents?.immediate).where(each => isObjectSchema(each))) {
for (const each of values(schema.parents?.immediate).where((each) => isObjectSchema(each))) {
this.fixCollisions(<ObjectSchema>each);
}
const [owned, flattened] = values(schema.properties).bifurcate(each => length(each.flattenedNames) === 0);
const [owned, flattened] = values(schema.properties).bifurcate((each) => length(each.flattenedNames) === 0);
const inherited = [...getAllParentProperties(schema)];
const all = [...owned, ...inherited, ...flattened];
@ -410,7 +465,10 @@ export class PreNamer {
for (const each of flattened /*.sort((a, b) => length(a.nameOptions) - length(b.nameOptions)) */) {
const ct = inlined.get(this.format.property(each.language.default.name));
if (ct && ct > 1) {
const options = getNameOptions(each.schema.language.default.name, [each.language.default.name, ...values(each.flattenedNames)]);
const options = getNameOptions(each.schema.language.default.name, [
each.language.default.name,
...values(each.flattenedNames),
]);
each.language.default.name = this.format.property(selectName(options, usedNames));
}
}

Просмотреть файл

@ -1,16 +1,24 @@
import { Session } from "@azure-tools/autorest-extension-base";
import { values, items, length, Dictionary, refCount, clone, keys } from "@azure-tools/linq";
import {
Model as oai3,
Refable,
Dereferenced,
dereference,
Schema,
PropertyDetails,
JsonType,
StringFormat,
} from "@azure-tools/openapi";
import { Session } from '@azure-tools/autorest-extension-base';
import { values, items, length, Dictionary, refCount, clone, keys } from '@azure-tools/linq';
import { Model as oai3, Refable, Dereferenced, dereference, Schema, PropertyDetails, JsonType, StringFormat } from '@azure-tools/openapi';
import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { Interpretations } from "../modeler/interpretations";
import { serialize } from '@azure-tools/codegen';
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { Interpretations } from '../modeler/interpretations';
import { getDiff } from 'recursive-diff'
import { getDiff } from "recursive-diff";
export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false;
const debug = (await host.GetValue("debug")) || false;
try {
const session = await startSession<oai3>(host);
@ -23,12 +31,22 @@ export async function processRequest(host: Host) {
const result = plugin.process();
// throw on errors.
if (!await session.getValue('ignore-errors', false)) {
if (!(await session.getValue("ignore-errors", false))) {
session.checkpoint();
}
host.WriteFile('prechecked-openapi-document.yaml', serialize(result, { sortKeys: false }), undefined, 'prechecked-openapi-document');
host.WriteFile('original-openapi-document.yaml', serialize(input, { sortKeys: false }), undefined, 'openapi-document');
host.WriteFile(
"prechecked-openapi-document.yaml",
serialize(result, { sortKeys: false }),
undefined,
"prechecked-openapi-document",
);
host.WriteFile(
"original-openapi-document.yaml",
serialize(input, { sortKeys: false }),
undefined,
"openapi-document",
);
} catch (E) {
if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);
@ -43,14 +61,14 @@ export class QualityPreChecker {
protected interpret: Interpretations;
constructor(protected session: Session<oai3>) {
this.input = session.model;// shadow(session.model, filename);
this.input = session.model; // shadow(session.model, filename);
this.interpret = new Interpretations(session);
}
async init() {
// get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {});
this.options = await this.session.getValue("modelerfour", {});
return this;
}
@ -59,35 +77,37 @@ export class QualityPreChecker {
return dereference(this.input, item);
}
getProperties(schema: Schema) {
return items(schema.properties).select(each => ({
return items(schema.properties).select((each) => ({
key: each.key,
name: <string>this.interpret.getPreferredName(each.value, each.key),
property: this.resolve(each.value).instance
property: this.resolve(each.value).instance,
}));
//return items(schema.properties).toMap(each => <string>this.interpret.getPreferredName(each.value, each.key), each => this.resolve(each.value).instance);
}
getSchemasFromArray(tag: string, schemas: Array<Refable<Schema>> | undefined): Iterable<{ name: string, schema: Schema, tag: string }> {
return values(schemas).select(a => {
getSchemasFromArray(
tag: string,
schemas: Array<Refable<Schema>> | undefined,
): Iterable<{ name: string; schema: Schema; tag: string }> {
return values(schemas).select((a) => {
const { instance: schema, name } = this.resolve(a);
return {
name: this.interpret.getName(name, schema),
schema,
tag
}
tag,
};
});
}
* getAllParents(tag: string, schema: Schema): Iterable<{ name: string, schema: Schema, tag: string }> {
*getAllParents(tag: string, schema: Schema): Iterable<{ name: string; schema: Schema; tag: string }> {
for (const parent of this.getSchemasFromArray(tag, schema.allOf)) {
yield parent;
yield* this.getAllParents(parent.name, parent.schema);
}
}
* getGrandParents(tag: string, schema: Schema): Iterable<{ name: string, schema: Schema, tag: string }> {
*getGrandParents(tag: string, schema: Schema): Iterable<{ name: string; schema: Schema; tag: string }> {
for (const parent of this.getSchemasFromArray(tag, schema.allOf)) {
yield* this.getAllParents(parent.name, parent.schema);
}
@ -99,40 +119,51 @@ export class QualityPreChecker {
}
completed.add(schema);
if (schema.allOf && schema.properties) {
const myProperties = this.getProperties(schema).toArray();
for (const { name: parentName, schema: parentSchema } of this.getAllParents(schemaName, schema)) {
this.checkForHiddenProperties(parentName, parentSchema, completed);
for (const { key, name: propName, property: parentProp } of this.getProperties(parentSchema)) {
const myProp = myProperties.find(each => each.name === propName);
const myProp = myProperties.find((each) => each.name === propName);
if (myProp) {
// check if the only thing different is the description.
const diff = getDiff(parentProp, myProp.property).filter(each => each.path[0] !== 'description' && each.path[0] !== 'x-ms-metadata');
const diff = getDiff(parentProp, myProp.property).filter(
(each) => each.path[0] !== "description" && each.path[0] !== "x-ms-metadata",
);
if (diff.length === 0) {
// the property didn't change except for description.
// the property didn't change except for description.
// we can let this go with a warning.
this.session.warning(`Schema '${schemaName}' has a property '${propName}' that is already declared the parent schema '${parentName}' but isn't significantly different. The property has been removed from ${schemaName}`, ['PreCheck', 'PropertyRedeclarationWarning']);
this.session.warning(
`Schema '${schemaName}' has a property '${propName}' that is already declared the parent schema '${parentName}' but isn't significantly different. The property has been removed from ${schemaName}`,
["PreCheck", "PropertyRedeclarationWarning"],
);
delete schema.properties[myProp.key];
continue;
}
if (diff.length === 1) {
// special case to yell about readonly changes
if (diff[0].path[0] === 'readOnly') {
this.session.warning(`Schema '${schemaName}' has a property '${propName}' that is already declared the parent schema '${parentName}' but 'readonly' has been changed -- this is not permitted. The property has been removed from ${schemaName}`, ['PreCheck', 'PropertyRedeclarationWarning']);
if (diff[0].path[0] === "readOnly") {
this.session.warning(
`Schema '${schemaName}' has a property '${propName}' that is already declared the parent schema '${parentName}' but 'readonly' has been changed -- this is not permitted. The property has been removed from ${schemaName}`,
["PreCheck", "PropertyRedeclarationWarning"],
);
delete schema.properties[myProp.key];
continue;
}
}
if (diff.length > 0) {
const details = diff.map(each => `${each.path.join('.')} => '${each.op === 'delete' ? '<removed>' : each.val}'`).join(',');
this.session.error(`Schema '${schemaName}' has a property '${propName}' that is conflicting with a property in the parent schema '${parentName}' differs more than just description : [${details}]`, ['PreCheck', 'PropertyRedeclaration']);
const details = diff
.map((each) => `${each.path.join(".")} => '${each.op === "delete" ? "<removed>" : each.val}'`)
.join(",");
this.session.error(
`Schema '${schemaName}' has a property '${propName}' that is conflicting with a property in the parent schema '${parentName}' differs more than just description : [${details}]`,
["PreCheck", "PropertyRedeclaration"],
);
continue;
}
}
@ -148,13 +179,15 @@ export class QualityPreChecker {
completed.add(schema);
if (schema.allOf) {
const grandParents = [...this.getGrandParents(schemaName, schema)];
const direct = [...this.getSchemasFromArray(schemaName, schema.allOf)];
for (const myParent of direct) {
for (const duplicate of grandParents.filter(each => each.schema === myParent.schema)) {
this.session.error(`Schema '${schemaName}' inherits '${duplicate.tag}' via an \`allOf\` that is already coming from parent '${myParent.name}'`, ['PreCheck', 'DuplicateInheritance']);
for (const duplicate of grandParents.filter((each) => each.schema === myParent.schema)) {
this.session.error(
`Schema '${schemaName}' inherits '${duplicate.tag}' via an \`allOf\` that is already coming from parent '${myParent.name}'`,
["PreCheck", "DuplicateInheritance"],
);
}
}
}
@ -168,7 +201,7 @@ export class QualityPreChecker {
return false;
case JsonType.String:
return schema.enum || schema['x-ms-enum'];
return schema.enum || schema["x-ms-enum"];
case JsonType.Object:
// empty objects don't worry.
@ -178,30 +211,33 @@ export class QualityPreChecker {
return true;
default:
return (length(schema.properties) > 0 || length(schema.allOf) > 0) ? true : false;
return length(schema.properties) > 0 || length(schema.allOf) > 0 ? true : false;
}
}
checkForDuplicateSchemas(): undefined {
this.session.warning('Checking for duplicate schemas, this could take a (long) while. Run with --verbose for more detail.', ['PreCheck', 'CheckDuplicateSchemas']);
this.session.warning(
"Checking for duplicate schemas, this could take a (long) while. Run with --verbose for more detail.",
["PreCheck", "CheckDuplicateSchemas"],
);
// Returns true if scanning should be restarted
const innerCheckForDuplicateSchemas = (): any => {
const errors = new Set<string>();
if (this.input.components && this.input.components.schemas) {
const dupedNames = items(this.input.components?.schemas)
.select(s => ({ key: s.key, value: this.resolve(s.value) }))
.select((s) => ({ key: s.key, value: this.resolve(s.value) }))
.groupBy(
// Make sure to check x-ms-client-name first to see if the schema is already being renamed
each => each.value.instance['x-ms-client-name'] || each.value.instance['x-ms-metadata']?.name,
each => each);
(each) => each.value.instance["x-ms-client-name"] || each.value.instance["x-ms-metadata"]?.name,
(each) => each,
);
for (const [name, schemas] of dupedNames.entries()) {
if (name && schemas.length > 1) {
const diff = getDiff(schemas[0].value.instance, schemas[1].value.instance).filter(each => each.path[0] !== 'description' && each.path[0] !== 'x-ms-metadata');
const diff = getDiff(schemas[0].value.instance, schemas[1].value.instance).filter(
(each) => each.path[0] !== "description" && each.path[0] !== "x-ms-metadata",
);
if (diff.length === 0) {
// found two schemas that are indeed the same.
@ -210,42 +246,67 @@ export class QualityPreChecker {
delete this.input.components.schemas[schemas[1].key];
const text = JSON.stringify(this.input);
this.input = JSON.parse(text.replace(new RegExp(`"\\#\\/components\\/schemas\\/${schemas[1].key}"`, 'g'), `"#/components/schemas/${schemas[0].key}"`));
this.input = JSON.parse(
text.replace(
new RegExp(`"\\#\\/components\\/schemas\\/${schemas[1].key}"`, "g"),
`"#/components/schemas/${schemas[0].key}"`,
),
);
// update metadata to match
if (this.input?.components?.schemas?.[schemas[0].key]) {
const primarySchema = this.resolve(this.input.components.schemas[schemas[0].key])
const primaryMetadata = primarySchema.instance['x-ms-metadata'];
const secondaryMetadata = schemas[1].value.instance['x-ms-metadata'];
const primarySchema = this.resolve(this.input.components.schemas[schemas[0].key]);
const primaryMetadata = primarySchema.instance["x-ms-metadata"];
const secondaryMetadata = schemas[1].value.instance["x-ms-metadata"];
if (primaryMetadata && secondaryMetadata) {
primaryMetadata.apiVersions = [...new Set<string>([...primaryMetadata.apiVersions || [], ...secondaryMetadata.apiVersions || []])]
primaryMetadata.filename = [...new Set<string>([...primaryMetadata.filename || [], ...secondaryMetadata.filename || []])]
primaryMetadata.originalLocations = [...new Set<string>([...primaryMetadata.originalLocations || [], ...secondaryMetadata.originalLocations || []])]
primaryMetadata['x-ms-secondary-file'] = !(!primaryMetadata['x-ms-secondary-file'] || !secondaryMetadata['x-ms-secondary-file'])
primaryMetadata.apiVersions = [
...new Set<string>([
...(primaryMetadata.apiVersions || []),
...(secondaryMetadata.apiVersions || []),
]),
];
primaryMetadata.filename = [
...new Set<string>([...(primaryMetadata.filename || []), ...(secondaryMetadata.filename || [])]),
];
primaryMetadata.originalLocations = [
...new Set<string>([
...(primaryMetadata.originalLocations || []),
...(secondaryMetadata.originalLocations || []),
]),
];
primaryMetadata["x-ms-secondary-file"] = !(
!primaryMetadata["x-ms-secondary-file"] || !secondaryMetadata["x-ms-secondary-file"]
);
}
}
this.session.verbose(`Schema ${name} has multiple identical declarations, reducing to just one - removing ${schemas[1].key} `, ['PreCheck', 'ReducingSchema']);
this.session.verbose(
`Schema ${name} has multiple identical declarations, reducing to just one - removing ${schemas[1].key} `,
["PreCheck", "ReducingSchema"],
);
// Restart the scan now that the duplicate has been removed
return true;
}
// it may not be identical, but if it's not an object, I'm not sure we care too much.
if (values(schemas).any(each => this.isObjectOrEnum(each.value.instance))) {
const rdiff = getDiff(schemas[1].value.instance, schemas[0].value.instance).filter(each => each.path[0] !== 'description' && each.path[0] !== 'x-ms-metadata');
if (values(schemas).any((each) => this.isObjectOrEnum(each.value.instance))) {
const rdiff = getDiff(schemas[1].value.instance, schemas[0].value.instance).filter(
(each) => each.path[0] !== "description" && each.path[0] !== "x-ms-metadata",
);
if (diff.length > 0) {
const details = diff.map(each => {
const path = each.path.join('.');
let iValue = each.op === 'add' ? '<none>' : JSON.stringify(each.oldVal);
if (each.op !== 'update') {
const v = rdiff.find(each => each.path.join('.') === path)
iValue = JSON.stringify(v?.val);
}
const nValue = each.op === 'delete' ? '<none>' : JSON.stringify(each.val);
return `${path}: ${iValue} => ${nValue}`;
}).join(',');
const details = diff
.map((each) => {
const path = each.path.join(".");
let iValue = each.op === "add" ? "<none>" : JSON.stringify(each.oldVal);
if (each.op !== "update") {
const v = rdiff.find((each) => each.path.join(".") === path);
iValue = JSON.stringify(v?.val);
}
const nValue = each.op === "delete" ? "<none>" : JSON.stringify(each.val);
return `${path}: ${iValue} => ${nValue}`;
})
.join(",");
errors.add(`Duplicate Schema named ${name} -- ${details} `);
continue;
}
@ -257,12 +318,15 @@ export class QualityPreChecker {
for (const each of errors) {
// Allow duplicate schemas if requested
if (!!this.options["lenient-model-deduplication"]) {
this.session.warning(each, ['PreCheck', 'DuplicateSchema']);
this.session.warning(each, ["PreCheck", "DuplicateSchema"]);
} else {
this.session.error(`${each}; This error can be *temporarily* avoided by using the 'modelerfour.lenient-model-deduplication' setting. NOTE: This setting will be removed in a future version of @autorest/modelerfour; schemas should be updated to fix this issue sooner than that.`, ['PreCheck', 'DuplicateSchema']);
this.session.error(
`${each}; This error can be *temporarily* avoided by using the 'modelerfour.lenient-model-deduplication' setting. NOTE: This setting will be removed in a future version of @autorest/modelerfour; schemas should be updated to fix this issue sooner than that.`,
["PreCheck", "DuplicateSchema"],
);
}
}
}
};
while (!!innerCheckForDuplicateSchemas()) {
// Loop until the scan is complete
@ -274,7 +338,10 @@ export class QualityPreChecker {
fixUpSchemasThatUseAllOfInsteadOfJustRef() {
const schemas = this.input.components?.schemas;
if (schemas) {
for (const { key, instance: schema, name, fromRef } of items(schemas).select(s => ({ key: s.key, ... this.resolve(s.value) }))) {
for (const { key, instance: schema, name, fromRef } of items(schemas).select((s) => ({
key: s.key,
...this.resolve(s.value),
}))) {
// we're looking for schemas that offer no possible value
// because they just use allOf instead of $ref
if (!schema.type || schema.type === JsonType.Object) {
@ -289,15 +356,26 @@ export class QualityPreChecker {
const $ref = schema?.allOf?.[0]?.$ref;
const text = JSON.stringify(this.input);
this.input = JSON.parse(text.replace(new RegExp(`"\\#\\/components\\/schemas\\/${key}"`, 'g'), `"${$ref}"`));
const location = schema['x-ms-metadata'].originalLocations[0].replace(/^.*\//, '')
if (schema['x-internal-autorest-anonymous-schema']) {
this.input = JSON.parse(
text.replace(new RegExp(`"\\#\\/components\\/schemas\\/${key}"`, "g"), `"${$ref}"`),
);
const location = schema["x-ms-metadata"].originalLocations[0].replace(/^.*\//, "");
if (schema["x-internal-autorest-anonymous-schema"]) {
delete schemas[key];
this.session.warning(`An anonymous inline schema for property '${location.replace(/-/g, '.')}' is using an 'allOf' instead of a $ref. This creates a wasteful anonymous type when generating code. Don't do that. - removing.`, ['PreCheck', 'AllOfWhenYouMeantRef']);
this.session.warning(
`An anonymous inline schema for property '${location.replace(
/-/g,
".",
)}' is using an 'allOf' instead of a $ref. This creates a wasteful anonymous type when generating code. Don't do that. - removing.`,
["PreCheck", "AllOfWhenYouMeantRef"],
);
} else {
// NOTE: Disabled removing of non-anonymous schema for now until
// it has been discussed in Azure/autorest.modelerfour#278
this.session.warning(`Schema '${location}' is using an 'allOf' instead of a $ref. This creates a wasteful anonymous type when generating code.`, ['PreCheck', 'AllOfWhenYouMeantRef']);
this.session.warning(
`Schema '${location}' is using an 'allOf' instead of a $ref. This creates a wasteful anonymous type when generating code.`,
["PreCheck", "AllOfWhenYouMeantRef"],
);
}
}
}
@ -306,12 +384,18 @@ export class QualityPreChecker {
}
fixUpObjectsWithoutType() {
for (const { instance: schema, name, fromRef } of values(this.input.components?.schemas).select(s => this.resolve(s))) {
if (<any>schema.type === 'file' || <any>schema.format === 'file' || <any>schema.format === 'binary') {
for (const { instance: schema, name, fromRef } of values(this.input.components?.schemas).select((s) =>
this.resolve(s),
)) {
if (<any>schema.type === "file" || <any>schema.format === "file" || <any>schema.format === "binary") {
// handle inconsistency in file format handling.
this.session.hint(
`'The schema ${schema?.['x-ms-metadata']?.name || name} with 'type: ${schema.type}', format: ${schema.format}' will be treated as a binary blob for binary media types.`,
['PreCheck', 'BinarySchema'], schema);
`'The schema ${schema?.["x-ms-metadata"]?.name || name} with 'type: ${schema.type}', format: ${
schema.format
}' will be treated as a binary blob for binary media types.`,
["PreCheck", "BinarySchema"],
schema,
);
schema.type = JsonType.String;
schema.format = StringFormat.Binary;
}
@ -323,14 +407,26 @@ export class QualityPreChecker {
// if the model has properties, then we're going to assume they meant to say JsonType.object
// but we're going to warn them anyway.
this.session.warning(`The schema '${schema?.['x-ms-metadata']?.name || name}' with an undefined type and decalared properties is a bit ambigious. This has been auto-corrected to 'type:object'`, ['PreCheck', 'SchemaMissingType'], schema);
this.session.warning(
`The schema '${
schema?.["x-ms-metadata"]?.name || name
}' with an undefined type and decalared properties is a bit ambigious. This has been auto-corrected to 'type:object'`,
["PreCheck", "SchemaMissingType"],
schema,
);
schema.type = JsonType.Object;
break;
}
if (schema.additionalProperties) {
// this looks like it's going to be a dictionary
// we'll mark it as object and let the processObjectSchema sort it out.
this.session.warning(`The schema '${schema?.['x-ms-metadata']?.name || name}' with an undefined type and additionalProperties is a bit ambigious. This has been auto-corrected to 'type:object'`, ['PreCheck', 'SchemaMissingType'], schema);
this.session.warning(
`The schema '${
schema?.["x-ms-metadata"]?.name || name
}' with an undefined type and additionalProperties is a bit ambigious. This has been auto-corrected to 'type:object'`,
["PreCheck", "SchemaMissingType"],
schema,
);
schema.type = JsonType.Object;
break;
}
@ -338,7 +434,13 @@ export class QualityPreChecker {
if (schema.allOf || schema.anyOf || schema.oneOf) {
// if the model has properties, then we're going to assume they meant to say JsonType.object
// but we're going to warn them anyway.
this.session.warning(`The schema '${schema?.['x-ms-metadata']?.name || name}' with an undefined type and 'allOf'/'anyOf'/'oneOf' is a bit ambigious. This has been auto-corrected to 'type:object'`, ['PreCheck', 'SchemaMissingType'], schema);
this.session.warning(
`The schema '${
schema?.["x-ms-metadata"]?.name || name
}' with an undefined type and 'allOf'/'anyOf'/'oneOf' is a bit ambigious. This has been auto-corrected to 'type:object'`,
["PreCheck", "SchemaMissingType"],
schema,
);
schema.type = JsonType.Object;
break;
}
@ -348,9 +450,7 @@ export class QualityPreChecker {
}
isEmptyObjectSchema(schema: Schema): boolean {
if (length(schema.properties) > 0 ||
length(schema.allOf) > 0 ||
schema.additionalProperties === true) {
if (length(schema.properties) > 0 || length(schema.allOf) > 0 || schema.additionalProperties === true) {
return false;
}
@ -365,15 +465,21 @@ export class QualityPreChecker {
fixUpSchemasWithEmptyObjectParent() {
const schemas = this.input.components?.schemas;
if (schemas) {
for (const { key, instance: schema, name, fromRef } of items(schemas).select(s => ({ key: s.key, ... this.resolve(s.value) }))) {
for (const { key, instance: schema, name, fromRef } of items(schemas).select((s) => ({
key: s.key,
...this.resolve(s.value),
}))) {
if (schema.type === JsonType.Object) {
if (length(schema.allOf) > 1) {
const schemaName = schema['x-ms-metadata']?.name || name;
schema.allOf = schema.allOf?.filter(p => {
const schemaName = schema["x-ms-metadata"]?.name || name;
schema.allOf = schema.allOf?.filter((p) => {
const parent = this.resolve(p).instance;
if (this.isEmptyObjectSchema(parent)) {
this.session.warning(`Schema '${schemaName}' has an allOf list with an empty object schema as a parent, removing it.`, ['PreCheck', 'EmptyParentSchemaWarning']);
this.session.warning(
`Schema '${schemaName}' has an allOf list with an empty object schema as a parent, removing it.`,
["PreCheck", "EmptyParentSchemaWarning"],
);
return false;
}
@ -385,10 +491,8 @@ export class QualityPreChecker {
}
}
process() {
this.fixUpSchemasThatUseAllOfInsteadOfJustRef()
this.fixUpSchemasThatUseAllOfInsteadOfJustRef();
this.fixUpObjectsWithoutType();
@ -397,17 +501,19 @@ export class QualityPreChecker {
this.checkForDuplicateSchemas();
let onlyOnce = new WeakSet<Schema>();
for (const { instance: schema, name, fromRef } of values(this.input.components?.schemas).select(s => this.resolve(s))) {
for (const { instance: schema, name, fromRef } of values(this.input.components?.schemas).select((s) =>
this.resolve(s),
)) {
this.checkForHiddenProperties(this.interpret.getName(name, schema), schema, onlyOnce);
}
onlyOnce = new WeakSet<Schema>();
for (const { instance: schema, name, fromRef } of values(this.input.components?.schemas).select(s => this.resolve(s))) {
for (const { instance: schema, name, fromRef } of values(this.input.components?.schemas).select((s) =>
this.resolve(s),
)) {
this.checkForDuplicateParents(this.interpret.getName(name, schema), schema, onlyOnce);
}
return this.input;
}
}

Просмотреть файл

@ -3,10 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { suite, test } from 'mocha-typescript';
import * as assert from 'assert';
import { suite, test } from "mocha-typescript";
import * as assert from "assert";
@suite class TestEnumNameInterpretation {
}
@suite
class TestEnumNameInterpretation {}

Просмотреть файл

@ -3,24 +3,24 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { suite, test } from 'mocha-typescript';
import * as assert from 'assert';
import { readFile, readdir, isDirectory } from '@azure-tools/async-io';
import { deserialize, fail, serialize } from '@azure-tools/codegen';
import { startSession, Message, Channel } from '@azure-tools/autorest-extension-base';
import { values, keys, length } from '@azure-tools/linq';
import { Model } from '@azure-tools/openapi';
import chalk from 'chalk';
import { QualityPreChecker } from '../quality-precheck/prechecker';
import { suite, test } from "mocha-typescript";
import * as assert from "assert";
import { readFile, readdir, isDirectory } from "@azure-tools/async-io";
import { deserialize, fail, serialize } from "@azure-tools/codegen";
import { startSession, Message, Channel } from "@azure-tools/autorest-extension-base";
import { values, keys, length } from "@azure-tools/linq";
import { Model } from "@azure-tools/openapi";
import chalk from "chalk";
import { QualityPreChecker } from "../quality-precheck/prechecker";
require('source-map-support').install();
require("source-map-support").install();
function addStyle(style: string, text: string): string {
return `▌PUSH:${style}${text}▌POP▐`;
}
function compileStyledText(text: string): string {
const styleStack = ['(x => x)'];
let result = '';
const styleStack = ["(x => x)"];
let result = "";
let consumedUpTo = 0;
const appendPart = (end: number) => {
const CHALK = chalk;
@ -31,10 +31,10 @@ function compileStyledText(text: string): string {
const commandRegex = /▌(.+?)▐/g;
let i: RegExpExecArray | null;
// eslint-disable-next-line no-cond-assign
while (i = commandRegex.exec(text)) {
while ((i = commandRegex.exec(text))) {
const startIndex = i.index;
const length = i[0].length;
const command = i[1].split(':');
const command = i[1].split(":");
// append up to here with current style
appendPart(startIndex);
@ -42,10 +42,10 @@ function compileStyledText(text: string): string {
// process command
consumedUpTo += length;
switch (command[0]) {
case 'PUSH':
styleStack.push('CHALK.' + command[1]);
case "PUSH":
styleStack.push("CHALK." + command[1]);
break;
case 'POP':
case "POP":
styleStack.pop();
break;
}
@ -55,28 +55,36 @@ function compileStyledText(text: string): string {
}
export function color(text: string): string {
return compileStyledText(text.
replace(/\*\*(.*?)\*\*/gm, addStyle('bold', '$1')).
replace(/(\[.*?s\])/gm, addStyle('yellow.bold', '$1')).
replace(/^# (.*)/gm, addStyle('greenBright', '$1')).
replace(/^## (.*)/gm, addStyle('green', '$1')).
replace(/^### (.*)/gm, addStyle('cyanBright', '$1')).
replace(/(https?:\/\/\S*)/gmi, addStyle('blue.bold.underline', '$1')).
replace(/__(.*)__/gm, addStyle('italic', '$1')).
replace(/^>(.*)/gm, addStyle('cyan', ' $1')).
replace(/^!(.*)/gm, addStyle('red.bold', ' $1')).
replace(/^(ERROR) (.*?):?(.*)/gmi, `${addStyle('red.bold', '$1')} ${addStyle('green', '$2')}:$3`).
replace(/^(WARNING) (.*?):?(.*)/gmi, `${addStyle('yellow.bold', '$1')} ${addStyle('green', '$2')}:$3`).
replace(/^(\s* - \w*:\/\/\S*):(\d*):(\d*) (.*)/gm, `${addStyle('cyan', '$1')}:${addStyle('cyan.bold', '$2')}:${addStyle('cyan.bold', '$3')} $4`).
replace(/`(.+?)`/gm, addStyle('gray', '$1')).
replace(/"(.*?)"/gm, addStyle('gray', '"$1"')).
replace(/'(.*?)'/gm, addStyle('gray', '\'$1\'')));
return compileStyledText(
text
.replace(/\*\*(.*?)\*\*/gm, addStyle("bold", "$1"))
.replace(/(\[.*?s\])/gm, addStyle("yellow.bold", "$1"))
.replace(/^# (.*)/gm, addStyle("greenBright", "$1"))
.replace(/^## (.*)/gm, addStyle("green", "$1"))
.replace(/^### (.*)/gm, addStyle("cyanBright", "$1"))
.replace(/(https?:\/\/\S*)/gim, addStyle("blue.bold.underline", "$1"))
.replace(/__(.*)__/gm, addStyle("italic", "$1"))
.replace(/^>(.*)/gm, addStyle("cyan", " $1"))
.replace(/^!(.*)/gm, addStyle("red.bold", " $1"))
.replace(/^(ERROR) (.*?):?(.*)/gim, `${addStyle("red.bold", "$1")} ${addStyle("green", "$2")}:$3`)
.replace(/^(WARNING) (.*?):?(.*)/gim, `${addStyle("yellow.bold", "$1")} ${addStyle("green", "$2")}:$3`)
.replace(
/^(\s* - \w*:\/\/\S*):(\d*):(\d*) (.*)/gm,
`${addStyle("cyan", "$1")}:${addStyle("cyan.bold", "$2")}:${addStyle("cyan.bold", "$3")} $4`,
)
.replace(/`(.+?)`/gm, addStyle("gray", "$1"))
.replace(/"(.*?)"/gm, addStyle("gray", '"$1"'))
.replace(/'(.*?)'/gm, addStyle("gray", "'$1'")),
);
}
(<any>global).color = color;
let unexpectedErrorCount = 0;
async function readData(folder: string, ...files: Array<string>): Promise<Array<{ model: any; filename: string; content: string }>> {
async function readData(
folder: string,
...files: Array<string>
): Promise<Array<{ model: any; filename: string; content: string }>> {
const results = [];
for (const filename of files) {
const content = await readFile(`${folder}/${filename}`);
@ -84,42 +92,56 @@ async function readData(folder: string, ...files: Array<string>): Promise<Array<
results.push({
model,
filename,
content
content,
});
}
return results;
}
async function createTestSession<TInputModel>(config: any, folder: string, inputs: Array<string>, outputs: Array<string>) {
async function createTestSession<TInputModel>(
config: any,
folder: string,
inputs: Array<string>,
outputs: Array<string>,
) {
const filesInFolder = await readData(folder, ...inputs);
const expected = <any>deserialize((values(filesInFolder).first(each => each.filename === 'expected.yaml') || fail(`missing test file 'expected.yaml' in ${folder}`)).content, 'expected.yaml')
const expected = <any>(
deserialize(
(
values(filesInFolder).first((each) => each.filename === "expected.yaml") ||
fail(`missing test file 'expected.yaml' in ${folder}`)
).content,
"expected.yaml",
)
);
const unexpected = <any>{};
return {
session: await startSession<TInputModel>({
ReadFile: async (filename: string): Promise<string> => (values(filesInFolder).first(each => each.filename === filename) || fail(`missing input '${filename}'`)).content,
ReadFile: async (filename: string): Promise<string> =>
(values(filesInFolder).first((each) => each.filename === filename) || fail(`missing input '${filename}'`))
.content,
GetValue: async (key: string): Promise<any> => {
if (!key) {
return config;
}
return config[key];
},
ListInputs: async (artifactType?: string): Promise<Array<string>> => filesInFolder.map(each => each.filename),
ListInputs: async (artifactType?: string): Promise<Array<string>> => filesInFolder.map((each) => each.filename),
ProtectFiles: async (path: string): Promise<void> => {
// test
// test
},
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string): void => {
// test
// test
},
Message: (message: Message): void => {
if (expected[message.Channel]) {
const i = expected[message.Channel].indexOf(message.Text.trim());
if (i > -1) {
// expected message found. remove it
// expected message found. remove it
expected[message.Channel].splice(i, 1);
if (expected[message.Channel].length === 0) {
delete expected[message.Channel];
@ -133,35 +155,40 @@ async function createTestSession<TInputModel>(config: any, folder: string, input
unexpected[message.Channel].push(message.Text);
},
UpdateConfigurationFile: (filename: string, content: string): void => {
// test
// test
},
GetConfigurationFile: async (filename: string): Promise<string> => '',
GetConfigurationFile: async (filename: string): Promise<string> => "",
}),
expected,
unexpected
unexpected,
};
}
@suite class Process {
@suite
class Process {
// to generate the oai3 doc from an oai2 input:
// autorest --pipeline-model:v3 --input-file:./oai2.loaded.json --output-folder:. --verbose --debug --no-network-check ../test-configuration.md
@test async 'error-checks'() {
@test async "error-checks"() {
const folders = await readdir(`${__dirname}/../../test/errors/`);
for (const each of folders) {
const folder = `${__dirname}/../../test/errors/${each}`;
if (! await isDirectory(folder)) {
if (!(await isDirectory(folder))) {
continue;
}
console.log(`Expecting Errors From: ${folder}`);
const cfg = {
modelerfour: {
}
}
modelerfour: {},
};
const { session, expected, unexpected } = await createTestSession<Model>(cfg, folder, ['openapi-document.json', 'expected.yaml'], []);
const { session, expected, unexpected } = await createTestSession<Model>(
cfg,
folder,
["openapi-document.json", "expected.yaml"],
[],
);
// process OAI model
const prechecker = await new QualityPreChecker(session).init();
@ -169,9 +196,19 @@ async function createTestSession<TInputModel>(config: any, folder: string, input
// go!
await prechecker.process();
assert(unexpectedErrorCount === 0, `Unexpected messages encountered -- these should be in the 'unexpected.yaml' file:\n\n=========\n\n${serialize(unexpected)}\n\n=========`);
assert(
unexpectedErrorCount === 0,
`Unexpected messages encountered -- these should be in the 'unexpected.yaml' file:\n\n=========\n\n${serialize(
unexpected,
)}\n\n=========`,
);
assert(length(expected) === 0, `Did not hit expected messages -- the following are present in the 'expected.yaml' file, but not hit: \n\n=========\n\n${serialize(expected)}\n\n=========`);
assert(
length(expected) === 0,
`Did not hit expected messages -- the following are present in the 'expected.yaml' file, but not hit: \n\n=========\n\n${serialize(
expected,
)}\n\n=========`,
);
}
}
}
}

Просмотреть файл

@ -3,34 +3,31 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { suite, test } from 'mocha-typescript';
import * as assert from 'assert';
import { ModelerFour } from '../modeler/modelerfour';
import { readFile, writeFile, readdir, mkdir } from '@azure-tools/async-io';
import { deserialize, serialize, fail } from '@azure-tools/codegen';
import { startSession } from '@azure-tools/autorest-extension-base';
import { values } from '@azure-tools/linq';
import { CodeModel } from '@azure-tools/codemodel';
import { Model } from '@azure-tools/openapi';
import { codeModelSchema } from '@azure-tools/codemodel';
import { ReadUri } from '@azure-tools/uri';
import { PreNamer } from '../prenamer/prenamer';
import { Flattener } from '../flattener/flattener';
import { Grouper } from '../grouper/grouper';
import { Checker } from '../checker/checker';
import chalk from 'chalk';
require('source-map-support').install();
import { suite, test } from "mocha-typescript";
import * as assert from "assert";
import { ModelerFour } from "../modeler/modelerfour";
import { readFile, writeFile, readdir, mkdir } from "@azure-tools/async-io";
import { deserialize, serialize, fail } from "@azure-tools/codegen";
import { startSession } from "@azure-tools/autorest-extension-base";
import { values } from "@azure-tools/linq";
import { CodeModel } from "@azure-tools/codemodel";
import { Model } from "@azure-tools/openapi";
import { codeModelSchema } from "@azure-tools/codemodel";
import { ReadUri } from "@azure-tools/uri";
import { PreNamer } from "../prenamer/prenamer";
import { Flattener } from "../flattener/flattener";
import { Grouper } from "../grouper/grouper";
import { Checker } from "../checker/checker";
import chalk from "chalk";
require("source-map-support").install();
function addStyle(style: string, text: string): string {
return `▌PUSH:${style}${text}▌POP▐`;
}
function compileStyledText(text: string): string {
const styleStack = ['(x => x)'];
let result = '';
const styleStack = ["(x => x)"];
let result = "";
let consumedUpTo = 0;
const appendPart = (end: number) => {
const CHALK = chalk;
@ -41,10 +38,10 @@ function compileStyledText(text: string): string {
const commandRegex = /▌(.+?)▐/g;
let i: RegExpExecArray | null;
// eslint-disable-next-line no-cond-assign
while (i = commandRegex.exec(text)) {
while ((i = commandRegex.exec(text))) {
const startIndex = i.index;
const length = i[0].length;
const command = i[1].split(':');
const command = i[1].split(":");
// append up to here with current style
appendPart(startIndex);
@ -52,10 +49,10 @@ function compileStyledText(text: string): string {
// process command
consumedUpTo += length;
switch (command[0]) {
case 'PUSH':
styleStack.push('CHALK.' + command[1]);
case "PUSH":
styleStack.push("CHALK." + command[1]);
break;
case 'POP':
case "POP":
styleStack.pop();
break;
}
@ -65,22 +62,27 @@ function compileStyledText(text: string): string {
}
export function color(text: string): string {
return compileStyledText(text.
replace(/\*\*(.*?)\*\*/gm, addStyle('bold', '$1')).
replace(/(\[.*?s\])/gm, addStyle('yellow.bold', '$1')).
replace(/^# (.*)/gm, addStyle('greenBright', '$1')).
replace(/^## (.*)/gm, addStyle('green', '$1')).
replace(/^### (.*)/gm, addStyle('cyanBright', '$1')).
replace(/(https?:\/\/\S*)/gmi, addStyle('blue.bold.underline', '$1')).
replace(/__(.*)__/gm, addStyle('italic', '$1')).
replace(/^>(.*)/gm, addStyle('cyan', ' $1')).
replace(/^!(.*)/gm, addStyle('red.bold', ' $1')).
replace(/^(ERROR) (.*?):?(.*)/gmi, `${addStyle('red.bold', '$1')} ${addStyle('green', '$2')}:$3`).
replace(/^(WARNING) (.*?):?(.*)/gmi, `${addStyle('yellow.bold', '$1')} ${addStyle('green', '$2')}:$3`).
replace(/^(\s* - \w*:\/\/\S*):(\d*):(\d*) (.*)/gm, `${addStyle('cyan', '$1')}:${addStyle('cyan.bold', '$2')}:${addStyle('cyan.bold', '$3')} $4`).
replace(/`(.+?)`/gm, addStyle('gray', '$1')).
replace(/"(.*?)"/gm, addStyle('gray', '"$1"')).
replace(/'(.*?)'/gm, addStyle('gray', '\'$1\'')));
return compileStyledText(
text
.replace(/\*\*(.*?)\*\*/gm, addStyle("bold", "$1"))
.replace(/(\[.*?s\])/gm, addStyle("yellow.bold", "$1"))
.replace(/^# (.*)/gm, addStyle("greenBright", "$1"))
.replace(/^## (.*)/gm, addStyle("green", "$1"))
.replace(/^### (.*)/gm, addStyle("cyanBright", "$1"))
.replace(/(https?:\/\/\S*)/gim, addStyle("blue.bold.underline", "$1"))
.replace(/__(.*)__/gm, addStyle("italic", "$1"))
.replace(/^>(.*)/gm, addStyle("cyan", " $1"))
.replace(/^!(.*)/gm, addStyle("red.bold", " $1"))
.replace(/^(ERROR) (.*?):?(.*)/gim, `${addStyle("red.bold", "$1")} ${addStyle("green", "$2")}:$3`)
.replace(/^(WARNING) (.*?):?(.*)/gim, `${addStyle("yellow.bold", "$1")} ${addStyle("green", "$2")}:$3`)
.replace(
/^(\s* - \w*:\/\/\S*):(\d*):(\d*) (.*)/gm,
`${addStyle("cyan", "$1")}:${addStyle("cyan.bold", "$2")}:${addStyle("cyan.bold", "$3")} $4`,
)
.replace(/`(.+?)`/gm, addStyle("gray", "$1"))
.replace(/"(.*?)"/gm, addStyle("gray", '"$1"'))
.replace(/'(.*?)'/gm, addStyle("gray", "'$1'")),
);
}
(<any>global).color = color;
@ -88,7 +90,10 @@ let errorCount = 0;
const resources = `${__dirname}/../../test/resources/process`;
async function readData(folder: string, ...files: Array<string>): Promise<Array<{ model: any; filename: string; content: string }>> {
async function readData(
folder: string,
...files: Array<string>
): Promise<Array<{ model: any; filename: string; content: string }>> {
const results = [];
for (const filename of files) {
const content = await readFile(`${folder}/${filename}`);
@ -96,128 +101,138 @@ async function readData(folder: string, ...files: Array<string>): Promise<Array<
results.push({
model,
filename,
content
content,
});
}
return results;
}
async function cts<TInputModel>(config: any, filename: string, content: string) {
const ii = [{
model: deserialize<any>(content, filename),
filename,
content
}];
const ii = [
{
model: deserialize<any>(content, filename),
filename,
content,
},
];
return await startSession<TInputModel>({
ReadFile: async (filename: string): Promise<string> => (values(ii).first(each => each.filename === filename) || fail(`missing input '${filename}'`)).content,
ReadFile: async (filename: string): Promise<string> =>
(values(ii).first((each) => each.filename === filename) || fail(`missing input '${filename}'`)).content,
GetValue: async (key: string): Promise<any> => {
if (!key) {
return config;
}
return config[key];
},
ListInputs: async (artifactType?: string): Promise<Array<string>> => ii.map(each => each.filename),
ListInputs: async (artifactType?: string): Promise<Array<string>> => ii.map((each) => each.filename),
ProtectFiles: async (path: string): Promise<void> => {
// test
// test
},
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string): void => {
// test
// test
},
Message: (message: any): void => {
// test
if (message.Channel === 'warning' || message.Channel === 'error' || message.Channel === 'verbose') {
if (message.Channel === 'error') {
// test
if (message.Channel === "warning" || message.Channel === "error" || message.Channel === "verbose") {
if (message.Channel === "error") {
errorCount++;
}
console.error(color(`${message.Channel} ${message.Text}`));
}
},
UpdateConfigurationFile: (filename: string, content: string): void => {
// test
// test
},
GetConfigurationFile: async (filename: string): Promise<string> => '',
GetConfigurationFile: async (filename: string): Promise<string> => "",
});
}
async function createTestSession<TInputModel>(config: any, folder: string, inputs: Array<string>, outputs: Array<string>) {
async function createTestSession<TInputModel>(
config: any,
folder: string,
inputs: Array<string>,
outputs: Array<string>,
) {
const ii = await readData(folder, ...inputs);
const oo = await readData(folder, ...outputs);
return await startSession<TInputModel>({
ReadFile: async (filename: string): Promise<string> => (values(ii).first(each => each.filename === filename) || fail(`missing input '${filename}'`)).content,
ReadFile: async (filename: string): Promise<string> =>
(values(ii).first((each) => each.filename === filename) || fail(`missing input '${filename}'`)).content,
GetValue: async (key: string): Promise<any> => {
if (!key) {
return config;
}
return config[key];
},
ListInputs: async (artifactType?: string): Promise<Array<string>> => ii.map(each => each.filename),
ListInputs: async (artifactType?: string): Promise<Array<string>> => ii.map((each) => each.filename),
ProtectFiles: async (path: string): Promise<void> => {
// test
// test
},
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string): void => {
// test
// test
},
Message: (message: any): void => {
// test
if (message.Channel === 'warning' || message.Channel === 'error' || message.Channel === 'verbose') {
if (message.Channel === 'error') {
// test
if (message.Channel === "warning" || message.Channel === "error" || message.Channel === "verbose") {
if (message.Channel === "error") {
errorCount++;
}
console.error(color(`${message.Channel} ${message.Text}`));
}
},
UpdateConfigurationFile: (filename: string, content: string): void => {
// test
// test
},
GetConfigurationFile: async (filename: string): Promise<string> => '',
GetConfigurationFile: async (filename: string): Promise<string> => "",
});
}
async function createPassThruSession(config: any, input: string, inputArtifactType: string) {
return await startSession<CodeModel>({
ReadFile: async (filename: string): Promise<string> => input,
GetValue: async (key: string): Promise<any> => {
if (!key) {
return config;
}
return config[key];
},
ListInputs: async (artifactType?: string): Promise<Array<string>> => [inputArtifactType],
ProtectFiles: async (path: string): Promise<void> => {
// test
},
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string): void => {
// test
},
Message: (message: any): void => {
// test
if (message.Channel === 'warning' || message.Channel === 'error' || message.Channel === 'verbose') {
if (message.Channel === 'error') {
errorCount++;
return await startSession<CodeModel>(
{
ReadFile: async (filename: string): Promise<string> => input,
GetValue: async (key: string): Promise<any> => {
if (!key) {
return config;
}
console.error(color(`${message.Channel} ${message.Text}`));
}
return config[key];
},
ListInputs: async (artifactType?: string): Promise<Array<string>> => [inputArtifactType],
ProtectFiles: async (path: string): Promise<void> => {
// test
},
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string): void => {
// test
},
Message: (message: any): void => {
// test
if (message.Channel === "warning" || message.Channel === "error" || message.Channel === "verbose") {
if (message.Channel === "error") {
errorCount++;
}
console.error(color(`${message.Channel} ${message.Text}`));
}
},
UpdateConfigurationFile: (filename: string, content: string): void => {
// test
},
GetConfigurationFile: async (filename: string): Promise<string> => "",
},
UpdateConfigurationFile: (filename: string, content: string): void => {
// test
},
GetConfigurationFile: async (filename: string): Promise<string> => '',
}, {}, codeModelSchema);
{},
codeModelSchema,
);
}
@suite
class Process {
@test
async 'simple model test'() {
const session = await createTestSession<Model>({}, resources, ['input2.yaml'], ['output1.yaml']);
async "simple model test"() {
const session = await createTestSession<Model>({}, resources, ["input2.yaml"], ["output1.yaml"]);
// process OAI model
const modeler = await new ModelerFour(session).init();
@ -230,32 +245,32 @@ class Process {
//await (writeFile(`${__dirname}/../../output.yaml`, yaml));
const cms = deserialize<CodeModel>(yaml, 'foo.yaml', codeModelSchema);
const cms = deserialize<CodeModel>(yaml, "foo.yaml", codeModelSchema);
assert.strictEqual(true, cms instanceof CodeModel, 'Type Info is maintained in deserialization.');
assert.strictEqual(true, cms instanceof CodeModel, "Type Info is maintained in deserialization.");
}
@test
async 'acceptance-suite'() {
async "acceptance-suite"() {
const folders = await readdir(`${__dirname}/../../test/scenarios/`);
for (const each of folders) {
console.log(`Processing: ${each}`);
const cfg = {
modelerfour: {
'flatten-models': true,
'flatten-payloads': true,
'group-parameters': true,
'resolve-schema-name-collisons': true,
'additional-checks': true,
"modelerfour": {
"flatten-models": true,
"flatten-payloads": true,
"group-parameters": true,
"resolve-schema-name-collisons": true,
"additional-checks": true,
//'always-create-content-type-parameter': true,
naming: {
"naming": {
override: {
'$host': '$host',
'cmyk': 'CMYK'
$host: "$host",
cmyk: "CMYK",
},
local: "_ + camel",
constantParameter: 'pascal',
constantParameter: "pascal",
/*
for when playing with python style settings :
@ -268,12 +283,17 @@ class Process {
constant: 'uppercase',
type: 'pascalcase',
// */
}
},
},
'payload-flattening-threshold': 2
}
"payload-flattening-threshold": 2,
};
const session = await createTestSession<Model>(cfg, `${__dirname}/../../test/scenarios/${each}`, ['openapi-document.json'], []);
const session = await createTestSession<Model>(
cfg,
`${__dirname}/../../test/scenarios/${each}`,
["openapi-document.json"],
[],
);
// process OAI model
const modeler = await new ModelerFour(session).init();
@ -283,24 +303,24 @@ class Process {
const yaml = serialize(codeModel, codeModelSchema);
await mkdir(`${__dirname}/../../test/scenarios/${each}`);
await (writeFile(`${__dirname}/../../test/scenarios/${each}/modeler.yaml`, yaml));
await writeFile(`${__dirname}/../../test/scenarios/${each}/modeler.yaml`, yaml);
const flattener = await new Flattener(await createPassThruSession(cfg, yaml, 'code-model-v4')).init();
const flattener = await new Flattener(await createPassThruSession(cfg, yaml, "code-model-v4")).init();
const flattened = await flattener.process();
const flatteneyaml = serialize(flattened, codeModelSchema);
await (writeFile(`${__dirname}/../../test/scenarios/${each}/flattened.yaml`, flatteneyaml));
await writeFile(`${__dirname}/../../test/scenarios/${each}/flattened.yaml`, flatteneyaml);
const grouper = await new Grouper(await createPassThruSession(cfg, flatteneyaml, 'code-model-v4')).init();
const grouper = await new Grouper(await createPassThruSession(cfg, flatteneyaml, "code-model-v4")).init();
const grouped = await grouper.process();
const groupedYaml = serialize(grouped, codeModelSchema);
await (writeFile(`${__dirname}/../../test/scenarios/${each}/grouped.yaml`, groupedYaml));
await writeFile(`${__dirname}/../../test/scenarios/${each}/grouped.yaml`, groupedYaml);
const namer = await new PreNamer(await createPassThruSession(cfg, groupedYaml, 'code-model-v4')).init();
const namer = await new PreNamer(await createPassThruSession(cfg, groupedYaml, "code-model-v4")).init();
const named = await namer.process();
const namedyaml = serialize(named, codeModelSchema);
await (writeFile(`${__dirname}/../../test/scenarios/${each}/namer.yaml`, namedyaml));
await writeFile(`${__dirname}/../../test/scenarios/${each}/namer.yaml`, namedyaml);
const checker = await new Checker(await createPassThruSession(cfg, namedyaml, 'code-model-v4')).init();
const checker = await new Checker(await createPassThruSession(cfg, namedyaml, "code-model-v4")).init();
await checker.process();
assert(errorCount === 0, "Errors Encountered");

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -9,7 +9,7 @@ import {
Schema,
PropertyDetails,
JsonType,
StringFormat
StringFormat,
} from "@azure-tools/openapi";
import {
createTestSession,
@ -18,7 +18,7 @@ import {
addOperation,
response,
InitialTestSpec,
responses
responses,
} from "./unitTestUtil";
class PreCheckerClient {
@ -52,35 +52,29 @@ class PreChecker {
nullable: true,
properties: {
hack: {
type: "boolean"
}
}
type: "boolean",
},
},
});
addSchema(spec, "ChildSchema", {
type: "object",
allOf: [
{ type: "object" },
{ $ref: "#/components/schemas/ParentSchema" }
],
allOf: [{ type: "object" }, { $ref: "#/components/schemas/ParentSchema" }],
properties: {
childOfHack: {
type: "integer"
}
}
type: "integer",
},
},
});
const client = await PreCheckerClient.create(spec);
const model = client.result;
const childSchemaRef =
model.components?.schemas && model.components?.schemas["ChildSchema"];
const childSchemaRef = model.components?.schemas && model.components?.schemas["ChildSchema"];
if (childSchemaRef) {
const childSchema = client.resolve<Schema>(childSchemaRef);
assert.strictEqual(childSchema.instance.allOf?.length, 1);
const parent = client.resolve(
childSchema.instance.allOf && childSchema.instance.allOf[0]
);
const parent = client.resolve(childSchema.instance.allOf && childSchema.instance.allOf[0]);
assert.strictEqual(parent.name, "ParentSchema");
} else {
assert.fail("No 'ChildSchema' found!");

Просмотреть файл

@ -4,54 +4,37 @@ import { values, clone } from "@azure-tools/linq";
import { Model } from "@azure-tools/openapi";
import { codeModelSchema } from "@azure-tools/codemodel";
export async function createTestSession(
config: any,
openApiModel: any,
messageList: any[]
) {
export async function createTestSession(config: any, openApiModel: any, messageList: any[]) {
const openApiText = JSON.stringify(openApiModel);
const ii = [
{
model: openApiModel as Model,
filename: "openapi-3.json",
content: openApiText
}
content: openApiText,
},
];
return await startSession<Model>(
{
ReadFile: async (filename: string): Promise<string> =>
(
values(ii).first(each => each.filename === filename) ||
fail(`missing input '${filename}'`)
).content,
(values(ii).first((each) => each.filename === filename) || fail(`missing input '${filename}'`)).content,
GetValue: async (key: string): Promise<any> => {
if (!key) {
return config;
}
return config[key];
},
ListInputs: async (artifactType?: string): Promise<Array<string>> =>
ii.map(each => each.filename),
ListInputs: async (artifactType?: string): Promise<Array<string>> => ii.map((each) => each.filename),
ProtectFiles: async (path: string): Promise<void> => {
// test
},
WriteFile: (
filename: string,
content: string,
sourceMap?: any,
artifactType?: string
): void => {
WriteFile: (filename: string, content: string, sourceMap?: any, artifactType?: string): void => {
// test
},
Message: (message: any): void => {
// test
if (
message.Channel === "warning" ||
message.Channel === "error" ||
message.Channel === "verbose"
) {
if (message.Channel === "warning" || message.Channel === "error" || message.Channel === "verbose") {
if (message.Channel === "error") {
messageList.push(message);
}
@ -60,10 +43,10 @@ export async function createTestSession(
UpdateConfigurationFile: (filename: string, content: string): void => {
// test
},
GetConfigurationFile: async (filename: string): Promise<string> => ""
GetConfigurationFile: async (filename: string): Promise<string> => "",
},
{},
codeModelSchema
codeModelSchema,
);
}
@ -72,26 +55,23 @@ export function response(
contentType: string,
schema: any,
description: string = "The response.",
extraProperties?: any
extraProperties?: any,
) {
return {
[code]: {
description,
content: {
[contentType]: {
schema
}
schema,
},
},
...extraProperties
}
...extraProperties,
},
};
}
export function responses(...responses: any[]) {
return responses.reduce(
(responsesDict, response) => Object.assign(responsesDict, response),
{}
);
return responses.reduce((responsesDict, response) => Object.assign(responsesDict, response), {});
}
export function properties(...properties: any[]) {
@ -105,44 +85,36 @@ export const InitialTestSpec = {
contact: {
name: "Microsoft Corporation",
url: "https://microsoft.com",
email: "devnull@microsoft.com"
email: "devnull@microsoft.com",
},
license: "MIT",
version: "1.0"
version: "1.0",
},
paths: {},
components: {
schemas: {}
}
schemas: {},
},
};
export type TestSpecCustomizer = (spec: any) => any;
export function createTestSpec(...customizers: TestSpecCustomizer[]): any {
return customizers.reduce<any>(
(spec: any, customizer: TestSpecCustomizer) => {
return customizer(spec);
},
clone(InitialTestSpec)
);
return customizers.reduce<any>((spec: any, customizer: TestSpecCustomizer) => {
return customizer(spec);
}, clone(InitialTestSpec));
}
export function addOperation(
spec: any,
path: string,
operationDict: any,
metadata: any = { apiVersions: ["1.0.0"] }
metadata: any = { apiVersions: ["1.0.0"] },
): void {
operationDict = { ...operationDict, ...{ "x-ms-metadata": metadata } };
spec.paths[path] = operationDict;
}
export function addSchema(
spec: any,
name: string,
schemaDict: any,
metadata: any = { apiVersions: ["1.0.0"] }
): void {
export function addSchema(spec: any, name: string, schemaDict: any, metadata: any = { apiVersions: ["1.0.0"] }): void {
schemaDict = { ...schemaDict, ...{ "x-ms-metadata": metadata } };
spec.components.schemas[name] = schemaDict;
}

Просмотреть файл

@ -3,17 +3,8 @@
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"types": [
"mocha"
]
"types": ["mocha"]
},
"include": [
"."
],
"exclude": [
"dist",
"resources",
"node_modules",
"**/*.d.ts"
]
"include": ["."],
"exclude": ["dist", "resources", "node_modules", "**/*.d.ts"]
}

Просмотреть файл

@ -101,4 +101,4 @@
"shouldPublish": true
}
]
}
}

Просмотреть файл

@ -14,13 +14,9 @@
"stripInternal": true,
"noEmitHelpers": false,
"target": "es2018",
"types": [
"node"
],
"lib": [
"es2018"
],
"types": ["node"],
"lib": ["es2018"],
"experimentalDecorators": true,
"newLine": "LF"
}
}
}