зеркало из
1
0
Форкнуть 0

Merge pull request #364 from Azure/internal/prettier

Internal: Configure prettier and eslint + fix
This commit is contained in:
Timothee Guerin 2020-12-03 15:20:24 -08:00 коммит произвёл GitHub
Родитель 8024c8d4d5 af119e28fc
Коммит 8b75335858
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
41 изменённых файлов: 3209 добавлений и 2432 удалений

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

@ -1,61 +1,50 @@
--- ---
parser: "@typescript-eslint/parser" parser: "@typescript-eslint/parser"
plugins: plugins:
- "@typescript-eslint" - "@typescript-eslint"
- prettier
env: env:
es6: true es6: true
node: true node: true
extends: extends:
- eslint:recommended - eslint:recommended
- plugin:@typescript-eslint/recommended - plugin:@typescript-eslint/recommended
globals: globals:
Atomics: readonly Atomics: readonly
SharedArrayBuffer: readonly SharedArrayBuffer: readonly
parserOptions: parserOptions:
ecmaVersion: 2018 ecmaVersion: 2018
sourceType: module sourceType: module
warnOnUnsupportedTypeScriptVersion : false warnOnUnsupportedTypeScriptVersion: false
rules: rules:
"@typescript-eslint/no-this-alias" : 'off' "@typescript-eslint/no-this-alias": "off"
"@typescript-eslint/interface-name-prefix": 'off' "@typescript-eslint/interface-name-prefix": "off"
"@typescript-eslint/explicit-function-return-type": 'off' "@typescript-eslint/explicit-function-return-type": "off"
"@typescript-eslint/no-explicit-any": 'off' "@typescript-eslint/no-explicit-any": "off"
"@typescript-eslint/no-empty-interface": 'off' "@typescript-eslint/no-empty-interface": "off"
"@typescript-eslint/no-namespace": 'off' "@typescript-eslint/no-namespace": "off"
"@typescript-eslint/explicit-member-accessibility": 'off' "@typescript-eslint/explicit-member-accessibility": "off"
"@typescript-eslint/no-unused-vars": 'off' "@typescript-eslint/no-unused-vars": "off"
"@typescript-eslint/no-parameter-properties": 'off' "@typescript-eslint/no-parameter-properties": "off"
"@typescript-eslint/no-angle-bracket-type-assertion" : 'off' "@typescript-eslint/no-angle-bracket-type-assertion": "off"
"require-atomic-updates" : 'off' "@typescript-eslint/no-non-null-assertion": "off"
'@typescript-eslint/consistent-type-assertions' : "@typescript-eslint/no-use-before-define": "off"
- error "require-atomic-updates": "off"
- assertionStyle: 'angle-bracket' "@typescript-eslint/consistent-type-assertions": "off"
"@typescript-eslint/array-type": "@typescript-eslint/array-type":
- error - warn
- default: generic - default: generic
indent: no-case-declarations: "off"
- warn no-undef: "off"
- 2 no-unused-vars: "off"
- SwitchCase : 1
"@typescript-eslint/indent":
- 0
- 2
no-undef: 'off'
no-unused-vars: 'off'
linebreak-style: linebreak-style:
- 'error' - "warn"
- unix - unix
quotes:
- error
- single
semi: semi:
- error - warn
- always - always
no-multiple-empty-lines: no-multiple-empty-lines:
- error - warn
- max: 2 - max: 2
maxBOF: 0 maxBOF: 0
maxEOF: 1 maxEOF: 1

331
.prettierignore Normal file
Просмотреть файл

@ -0,0 +1,331 @@
#-------------------------------------------------------------------------------------------------------------------
# Keep this section in sync with .gitignore
#-------------------------------------------------------------------------------------------------------------------
## Ignore generated code
PackageTest/NugetPackageTest/Generated
src/generator/AutoRest.NodeJS.Tests/AcceptanceTests/*.js
## Ignore user-specific files, temporary files, build results, etc.
compare-results/*
# User-specific files
*.suo
*.user
*.sln.docstates
.vs
launchSettings.json
# Build results
binaries/
[Dd]ebug*/
[Rr]elease/
[Tt]est[Rr]esult*
[Bb]uild[Ll]og.*
[Bb]uild.out
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
*.vssscc
.builds
*.pidb
*.log*
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# VS Code settings
*.vscode
# Code analysis
*.CodeAnalysisLog.xml
# Guidance Automation Toolkit
*.gpState
# ReSharper is a Visual Studio add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.[Pp]ublish.xml
# Others
[Bb]in
[Oo]bj
sql
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
# Build tasks
[Tt]ools/*.dll
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# Azure Tooling #
node_modules
.ntvs_analysis.dat
# Eclipse #
*.pydevproject
.project
.metadata
bin/**
tmp/**
tmp/**/*
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# Xamarin #
*.userprefs
# Other Tooling #
.classpath
.project
target
build
reports
.gradle
.idea
*.iml
Tools/7-Zip
.gitrevision
# Sensitive files
*.keys
*.pfx
*.cer
*.pem
*.jks
# Backup & report files from converting a project to a new version of VS.
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
# NuGet
packages
# Mac OS #
.DS_Store
.DS_Store?
# Windows #
Thumbs.db
# Mono
*dll.mdb
*exe.mdb
#old nuget restore folder
.nuget/
src/generator/AutoRest.Ruby*Tests/Gemfile.lock
src/generator/AutoRest.Ruby*/*/RspecTests/Generated/*
#netcore
/NetCore
*.lock.json
#dnx installation
dnx-clr-win-x86*/
dnx-coreclr-win-x86*/
/dnx
# Gemfile.lock
Gemfile.lock
# go ignore
src/generator/AutoRest.Go.Tests/pkg/*
src/generator/AutoRest.Go.Tests/bin/*
src/generator/AutoRest.Go.Tests/src/github.com/*
src/generator/AutoRest.Go.Tests/src/tests/generated/*
src/generator/AutoRest.Go.Tests/src/tests/vendor/*
src/generator/AutoRest.Go.Tests/src/tests/glide.lock
autorest/**/*.js
core/**/*.js
*.js.map
# backup files
*~
#client runtime
src/client/**/*
src/extension/old/**/*
*.d.ts
src/bootstrapper
src/extension/out
src/next-gen
package/nuget/tools
package/chocolatey/*.nupkg
Samples/**/*.map
# npm (we do want to test for most recent versions)
**/package-lock.json
**/dist/
src/*/nm
/nm/
*.tgz
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# Common toolchain intermediate files
temp
# Rush files
common/temp/**
package-deps.json
common/config/rush/shrinkwrap.yaml
# Code generation output for regression tests
core/test/regression
#-------------------------------------------------------------------------------------------------------------------
# Prettier-specific overrides
#-------------------------------------------------------------------------------------------------------------------
# Rush files
common/changes/
common/scripts/
common/config/
CHANGELOG.*
# Package manager files
pnpm-lock.yaml
yarn.lock
package-lock.json
shrinkwrap.json
# Build outputs
dist
lib
# Prettier reformats code blocks inside Markdown, which affects rendered output
*.md
schema/
Samples/
modelerfour/test/resources/
modelerfour/test/scenarios/
modelerfour/test/errors/

5
.prettierrc.yml Normal file
Просмотреть файл

@ -0,0 +1,5 @@
trailingComma: "all"
printWidth: 120
quoteProps: "consistent"
endOfLine: "auto"
arrowParens: always

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

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

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

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

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

@ -1,2 +1,2 @@
// Runs the npm run command on each project that has it. // 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 # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger: trigger:
- master - master
pool: pool:
vmImage: 'ubuntu-latest' vmImage: "ubuntu-latest"
steps: steps:
- task: NodeTool@0 - task: NodeTool@0
inputs: inputs:
versionSpec: '10.x' versionSpec: "10.x"
displayName: 'Install Node.js' displayName: "Install Node.js"
- script: | - script: |
# ensure latest npm is installed # ensure latest npm is installed
npm install -g npm npm install -g npm
npm install -g publish-release npm install -g publish-release
# make sure the versions are all synchronized and pull in dependencies # make sure the versions are all synchronized and pull in dependencies
npx @microsoft/rush sync-versions npx @microsoft/rush sync-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# set the actual package versions and update again # set the actual package versions and update again
npx @microsoft/rush set-versions npx @microsoft/rush set-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush sync-versions npx @microsoft/rush sync-versions
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
npx @microsoft/rush update npx @microsoft/rush update
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# compile the code # compile the code
npx @microsoft/rush rebuild npx @microsoft/rush rebuild
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# build the packages # build the packages
npx @microsoft/rush publish --publish --pack --include-all --tag latest npx @microsoft/rush publish --publish --pack --include-all --tag latest
rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
# publish autorest cli and beta (@autorest/autorest) # publish autorest cli and beta (@autorest/autorest)
cd modelerfour cd modelerfour
v=`node -e "console.log(require('./package.json').version)"` 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) 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 rc=$?; if [ $rc -ne 0 ]; then exit $rc ; fi
cd .. 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 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: specs:
- specRootPath: ../modelerfour/node_modules/@microsoft.azure/autorest.testserver/swagger - specRootPath: ../modelerfour/node_modules/@microsoft.azure/autorest.testserver/swagger
specPaths: specPaths:
- additionalProperties.json - additionalProperties.json
- azure-parameter-grouping.json - azure-parameter-grouping.json
- azure-report.json - azure-report.json
- azure-resource-x.json - azure-resource-x.json
- azure-resource.json - azure-resource.json
- azure-special-properties.json - azure-special-properties.json
- body-array.json - body-array.json
- body-boolean.json - body-boolean.json
- body-boolean.quirks.json - body-boolean.quirks.json
- body-byte.json - body-byte.json
- body-complex.json - body-complex.json
- body-date.json - body-date.json
- body-datetime-rfc1123.json - body-datetime-rfc1123.json
- body-datetime.json - body-datetime.json
- body-duration.json - body-duration.json
- body-dictionary.json - body-dictionary.json
- body-file.json - body-file.json
# Not yet supported by AutoRest v3 and Modelerfour # Not yet supported by AutoRest v3 and Modelerfour
# - body-formdata-urlencoded.json # - body-formdata-urlencoded.json
# - body-formdata.json # - body-formdata.json
- body-integer.json - body-integer.json
- body-number.json - body-number.json
- body-number.quirks.json - body-number.quirks.json
- body-string.json - body-string.json
- body-string.quirks.json - body-string.quirks.json
- complex-model.json - complex-model.json
- custom-baseUrl.json - custom-baseUrl.json
- custom-baseUrl-more-options.json - custom-baseUrl-more-options.json
- custom-baseUrl-paging.json - custom-baseUrl-paging.json
- extensible-enums-swagger.json - extensible-enums-swagger.json
- head-exceptions.json - head-exceptions.json
- head.json - head.json
- header.json - header.json
- httpInfrastructure.json - httpInfrastructure.json
- httpInfrastructure.quirks.json - httpInfrastructure.quirks.json
- lro.json - lro.json
- media_types.json - media_types.json
- model-flattening.json - model-flattening.json
- multiapi-v1.json - multiapi-v1.json
- multiapi-v2.json - multiapi-v2.json
- multiapi-v3.json - multiapi-v3.json
- object-type.json - object-type.json
- paging.json - paging.json
- parameter-flattening.json - parameter-flattening.json
- report.json - report.json
- required-optional.json - required-optional.json
- storage.json - storage.json
- subscriptionId-apiVersion.json - subscriptionId-apiVersion.json
- url-multi-collectionFormat.json - url-multi-collectionFormat.json
- url.json - url.json
- validation.json - validation.json
- xml-service.json - xml-service.json
- xms-error-responses.json - xms-error-responses.json
languages: languages:
- language: python - language: python
outputPath: ../modelerfour/test/regression/python outputPath: ../modelerfour/test/regression/python
@ -73,7 +73,7 @@ languages:
outputPath: ../modelerfour/test/regression/typescript outputPath: ../modelerfour/test/regression/typescript
excludeSpecs: excludeSpecs:
# The paging spec currently fails, will re-enable once it's fixed # The paging spec currently fails, will re-enable once it's fixed
- paging.json - paging.json
oldArgs: oldArgs:
- --v3 - --v3
- --package-name:test-package - --package-name:test-package

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

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

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

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

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

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

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

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

24
.vscode/settings.json поставляемый
Просмотреть файл

@ -23,11 +23,11 @@
"**/obj/**": true, "**/obj/**": true,
"**/bin/**": true "**/bin/**": true
}, },
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": { "[typescript]": {
"editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.formatOnPaste": true,
"editor.tabSize": 2,
"editor.detectIndentation": false,
}, },
"[json]": { "[json]": {
"editor.formatOnSave": true, "editor.formatOnSave": true,
@ -47,18 +47,10 @@
"command-line.json": "jsonc", "command-line.json": "jsonc",
"common-versions.json": "jsonc" "common-versions.json": "jsonc"
}, },
"eslint.autoFixOnSave": true, "editor.formatOnSave": true,
"eslint.validate": [ "editor.codeActionsOnSave": {
"javascript", "source.fixAll.eslint": true
{ },
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": false
}
],
"eslint.workingDirectories": [ "eslint.workingDirectories": [
{ {
"changeProcessCWD": true, "changeProcessCWD": true,

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

@ -2,8 +2,8 @@
# configure plugins first # configure plugins first
parser: "@typescript-eslint/parser" parser: "@typescript-eslint/parser"
plugins: plugins:
- "@typescript-eslint" - "@typescript-eslint"
# then inherit the common settings # then inherit the common settings
extends: 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 {
import { Session } from '@azure-tools/autorest-extension-base'; CodeModel,
import { values, items, length, Dictionary, refCount, clone } from '@azure-tools/linq'; 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 { export class Checker {
codeModel: CodeModel codeModel: CodeModel;
options: Dictionary<any> = {}; options: Dictionary<any> = {};
constructor(protected session: Session<CodeModel>) { constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename); this.codeModel = session.model; // shadow(session.model, filename);
} }
async init() { async init() {
// get our configuration for this run. // get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {}); this.options = await this.session.getValue("modelerfour", {});
return this; return this;
} }
checkOperationGroups() { 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 .`, []); this.session.error(`Duplicate Operation group '${dupe}' detected .`, []);
}; }
} }
checkOperations() { checkOperations() {
for (const group of this.codeModel.operationGroups) { 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.`, []); this.session.error(`Duplicate Operation '${dupe}' detected.`, []);
}; }
} }
} }
checkSchemas() { checkSchemas() {
const allSchemas = values(<Dictionary<Schema[]>><any>this.codeModel.schemas).selectMany(schemas => Array.isArray(schemas) ? values(schemas) : []).toArray(); const allSchemas = values(<Dictionary<Array<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)}'.`, []); 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() const types = values(<Array<Schema>>this.codeModel.schemas.objects)
for (const dupe of values(types).duplicates(each => each.language.default.name)) { .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.`, []); 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()) { /* for (const dupe of values(this.codeModel.schemas.numbers).select(each => each.type).duplicates()) {
this.session.error(`Duplicate '${dupe}' detected.`, []); this.session.error(`Duplicate '${dupe}' detected.`, []);
@ -48,7 +72,7 @@ export class Checker {
} }
process() { process() {
if (this.options['additional-checks'] !== false) { if (this.options["additional-checks"] !== false) {
this.checkOperationGroups(); this.checkOperationGroups();
this.checkOperations(); this.checkOperations();
@ -57,4 +81,4 @@ export class Checker {
} }
return this.codeModel; return this.codeModel;
} }
} }

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

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

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

@ -1,11 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
// load modules from static linker filesystem. // load modules from static linker filesystem.
try { 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/static-loader.js`).load(`${__dirname}/../dist/static_modules.fs`);
} }
require(`${__dirname}/../dist/main.js`); require(`${__dirname}/../dist/main.js`);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

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

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

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

@ -3,14 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen'; import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from '@azure-tools/autorest-extension-base'; import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel'; import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Example } from './example'; import { Example } from "./example";
export async function processRequest(host: Host) { export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false; const debug = (await host.GetValue("debug")) || false;
try { try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema); const session = await startSession<CodeModel>(host, {}, codeModelSchema);
@ -22,9 +21,8 @@ export async function processRequest(host: Host) {
const result = plugin.process(); const result = plugin.process();
// output the model to the pipeline // output the model to the pipeline
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4'); 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-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
} catch (E) { } catch (E) {
if (debug) { if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`); 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 {
import { Session } from '@azure-tools/autorest-extension-base'; CodeModel,
import { values, items, length, Dictionary, refCount, clone } from '@azure-tools/linq'; 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 xmsThreshold = "x-ms-payload-flattening-threshold";
const xmsFlatten = 'x-ms-client-flatten'; const xmsFlatten = "x-ms-client-flatten";
const isCurrentlyFlattening = 'x-ms-flattening'; const isCurrentlyFlattening = "x-ms-flattening";
const hasBeenFlattened = 'x-ms-flattened'; const hasBeenFlattened = "x-ms-flattened";
export class Flattener { export class Flattener {
codeModel: CodeModel codeModel: CodeModel;
options: Dictionary<any> = {}; options: Dictionary<any> = {};
threshold: number = 0; threshold = 0;
recursePayload: boolean = false; recursePayload = false;
constructor(protected session: Session<CodeModel>) { constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename); this.codeModel = session.model; // shadow(session.model, filename);
} }
async init() { async init() {
// get our configuration for this run. // get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {}); this.options = await this.session.getValue("modelerfour", {});
this.threshold = await this.session.getValue('payload-flattening-threshold', 0); this.threshold = await this.session.getValue("payload-flattening-threshold", 0);
this.recursePayload = await this.session.getValue('recursive-payload-flattening', false); this.recursePayload = await this.session.getValue("recursive-payload-flattening", false);
return this; 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) { if (property.readOnly) {
// skip read-only properties // skip read-only properties
return; return;
@ -35,29 +53,34 @@ export class Flattener {
yield* this.getFlattenedParameters(parameter, child, [...path, property]); yield* this.getFlattenedParameters(parameter, child, [...path, property]);
} }
} else { } else {
const vp = new VirtualParameter(property.language.default.name, property.language.default.description, property.schema, { const vp = new VirtualParameter(
...property, property.language.default.name,
implementation: ImplementationLocation.Method, property.language.default.description,
originalParameter: parameter, property.schema,
targetProperty: property, {
pathToProperty: path ...property,
}); implementation: ImplementationLocation.Method,
originalParameter: parameter,
targetProperty: property,
pathToProperty: path,
},
);
delete (<any>vp).serializedName; delete (<any>vp).serializedName;
delete (<any>vp).readOnly; delete (<any>vp).readOnly;
delete (<any>vp).isDiscriminator; delete (<any>vp).isDiscriminator;
delete (<any>vp).flattenedNames; 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 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']) { if (path.length === 0 && parameter.extensions?.["x-ms-parameter-grouping"]) {
(vp.extensions = vp.extensions || {})['x-ms-parameter-grouping'] = parameter.extensions?.['x-ms-parameter-grouping']; (vp.extensions = vp.extensions || {})["x-ms-parameter-grouping"] =
parameter.extensions?.["x-ms-parameter-grouping"];
} }
yield vp; 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) * 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) { flattenPayload(request: Request, parameter: Parameter, schema: ObjectSchema) {
@ -73,7 +96,6 @@ export class Flattener {
request.parameters?.push(vp); request.parameters?.push(vp);
} }
} }
} }
/** /**
@ -90,14 +112,16 @@ export class Flattener {
if (state === true) { if (state === true) {
// in progress. // 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. // hasn't started yet.
schema.extensions = schema.extensions || {}; schema.extensions = schema.extensions || {};
schema.extensions[isCurrentlyFlattening] = true; 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. // the problem when the order isn't just right.
for (const parent of values(schema.parents?.immediate)) { for (const parent of values(schema.parents?.immediate)) {
if (isObjectSchema(parent)) { if (isObjectSchema(parent)) {
@ -107,7 +131,6 @@ export class Flattener {
if (schema.properties) { if (schema.properties) {
for (const { key: index, value: property } of items(schema.properties).toArray().reverse()) { for (const { key: index, value: property } of items(schema.properties).toArray().reverse()) {
if (isObjectSchema(property.schema) && property.extensions?.[xmsFlatten]) { if (isObjectSchema(property.schema) && property.extensions?.[xmsFlatten]) {
// first, ensure tha the child is pre-flattened // first, ensure tha the child is pre-flattened
this.flattenSchema(property.schema); this.flattenSchema(property.schema);
@ -115,20 +138,30 @@ export class Flattener {
// remove that property from the scheama // remove that property from the scheama
schema.properties.splice(index, 1); schema.properties.splice(index, 1);
// copy all of the properties from the child into this // copy all of the properties from the child into this
// schema // schema
for (const childProperty of values(getAllProperties(property.schema))) { for (const childProperty of values(getAllProperties(property.schema))) {
schema.addProperty(new Property(childProperty.language.default.name, childProperty.language.default.description, childProperty.schema, { schema.addProperty(
...(<any>childProperty), new Property(
flattenedNames: [property.serializedName, ...childProperty.flattenedNames ? childProperty.flattenedNames : [childProperty.serializedName]], childProperty.language.default.name,
required: property.required && childProperty.required 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 // remove the extension
delete property.extensions[xmsFlatten]; delete property.extensions[xmsFlatten];
if (length(property.extensions) === 0) { if (length(property.extensions) === 0) {
delete property['extensions']; delete property["extensions"];
} }
// and mark the child class as 'do-not-generate' ? // and mark the child class as 'do-not-generate' ?
(property.schema.extensions = property.schema.extensions || {})[hasBeenFlattened] = true; (property.schema.extensions = property.schema.extensions || {})[hasBeenFlattened] = true;
@ -143,18 +176,17 @@ export class Flattener {
// support 'x-ms-payload-flattening-threshold' per-operation // support 'x-ms-payload-flattening-threshold' per-operation
// support '--payload-flattening-threshold:X' global setting // 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)) { for (const schema of values(this.codeModel.schemas.objects)) {
this.flattenSchema(schema); this.flattenSchema(schema);
} }
if (!this.options['keep-unused-flattened-models']) { if (!this.options["keep-unused-flattened-models"]) {
let dirty = false; let dirty = false;
do { do {
// reset on every pass // reset on every pass
dirty = false; dirty = false;
// remove unreferenced models // remove unreferenced models
for (const { key, value: schema } of items(this.codeModel.schemas.objects).toArray()) { for (const { key, value: schema } of items(this.codeModel.schemas.objects).toArray()) {
// only remove unreferenced models that have been flattened. // only remove unreferenced models that have been flattened.
if (!schema.extensions?.[hasBeenFlattened]) { if (!schema.extensions?.[hasBeenFlattened]) {
@ -162,12 +194,12 @@ export class Flattener {
} }
if (schema.discriminatorValue || schema.discriminator) { 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; continue;
} }
if (schema.children?.all || schema.parents?.all) { 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; continue;
} }
@ -180,34 +212,33 @@ export class Flattener {
} while (dirty); } while (dirty);
} }
for (const schema of values(this.codeModel.schemas.objects)) { for (const schema of values(this.codeModel.schemas.objects)) {
if (schema.extensions) { if (schema.extensions) {
delete schema.extensions[isCurrentlyFlattening]; delete schema.extensions[isCurrentlyFlattening];
// don't want this until I have removed the unreferenced models. // don't want this until I have removed the unreferenced models.
// delete schema.extensions[hasBeenFlattened]; // delete schema.extensions[hasBeenFlattened];
if (length(schema.extensions) === 0) { 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: * A body parameter is flattened (one level) when:
* *
* - the body parameter schema is an object * - the body parameter schema is an object
* - the body parameter schema is not polymorphic (is this true?) * - the body parameter schema is not polymorphic (is this true?)
* *
* *
* *
* and one of: * and one of:
* - the body parameter has x-ms-client-flatten: true * - 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 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 * - 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 // flatten payloads
@ -216,12 +247,15 @@ export class Flattener {
for (const operation of group.operations) { for (const operation of group.operations) {
// when there are multiple requests in an operation // when there are multiple requests in an operation
// and the generator asks not to flatten them // 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; continue;
} }
for (const request of values(operation.requests)) { 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)) { if (body && isObjectSchema(body.schema)) {
const schema = <ObjectSchema>body.schema; const schema = <ObjectSchema>body.schema;
@ -236,12 +270,16 @@ export class Flattener {
continue; continue;
} }
if (!flattenOperationPayload) { if (!flattenOperationPayload) {
const threshold = <number>operation.extensions?.[xmsThreshold] ?? this.threshold; const threshold = <number>operation.extensions?.[xmsThreshold] ?? this.threshold;
if (threshold > 0) { if (threshold > 0) {
// get the count of the (non-readonly) properties in the schema // 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen'; import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from '@azure-tools/autorest-extension-base'; import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel'; import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Flattener } from './flattener'; import { Flattener } from "./flattener";
export async function processRequest(host: Host) { export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false; const debug = (await host.GetValue("debug")) || false;
try { try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema); const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {}); const options = <any>await session.getValue("modelerfour", {});
// process // process
const plugin = await new Flattener(session).init(); const plugin = await new Flattener(session).init();
@ -23,14 +22,13 @@ export async function processRequest(host: Host) {
const result = plugin.process(); const result = plugin.process();
// output the model to the pipeline // output the model to the pipeline
if (options['emit-yaml-tags'] !== false) { if (options["emit-yaml-tags"] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4'); host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
} }
if (options['emit-yaml-tags'] !== true) { if (options["emit-yaml-tags"] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags'); host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
} }
} catch (E) { } catch (E) {
if (debug) { if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`); 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 {
import { Session } from '@azure-tools/autorest-extension-base'; CodeModel,
import { values, items, length, Dictionary, refCount, clone } from '@azure-tools/linq'; Schema,
import { pascalCase, camelCase } from '@azure-tools/codegen'; GroupSchema,
isObjectSchema,
const mergeReponseHeaders = 'merge-response-headers'; SchemaType,
const enableParameterGrouping = 'group-parameters'; GroupProperty,
const xmsParameterGrouping = 'x-ms-parameter-grouping' 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 { export class Grouper {
codeModel: CodeModel codeModel: CodeModel;
options: Dictionary<any> = {}; options: Dictionary<any> = {};
groups: Dictionary<GroupSchema> = {}; groups: Dictionary<GroupSchema> = {};
constructor(protected session: Session<CodeModel>) { constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename); this.codeModel = session.model; // shadow(session.model, filename);
} }
async init() { async init() {
// get our configuration for this run. // get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {}); this.options = await this.session.getValue("modelerfour", {});
return this; return this;
} }
process() { process() {
if (this.options[enableParameterGrouping] === true) { if (this.options[enableParameterGrouping] === true) {
for (const group of this.codeModel.operationGroups) { for (const group of this.codeModel.operationGroups) {
for (const operation of group.operations) { for (const operation of group.operations) {
for (const request of values(operation.requests)) { for (const request of values(operation.requests)) {
@ -53,17 +66,26 @@ export class Grouper {
proposedName(group: OperationGroup, operation: Operation, parameter: Parameter) { proposedName(group: OperationGroup, operation: Operation, parameter: Parameter) {
const xmsp = parameter.extensions?.[xmsParameterGrouping]; const xmsp = parameter.extensions?.[xmsParameterGrouping];
if (xmsp.name && typeof xmsp.name === 'string') { if (xmsp.name && typeof xmsp.name === "string") {
return xmsp.name; 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}`); return pascalCase(`${group.$key} ${operation.language.default.name} ${postfix}`);
} }
processParameterGroup(group: OperationGroup, operation: Operation, request: Request) { 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) { if (grouped.length > 0) {
// create a parameter group object schema for the selected parameters. // 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. // see if we've started the schema for this yet.
if (!this.groups[groupName]) { if (!this.groups[groupName]) {
// create a new object schema for this group // 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]; schema.usage = [SchemaContext.Input];
this.groups[groupName] = schema; this.groups[groupName] = schema;
this.codeModel.schemas.add(schema); this.codeModel.schemas.add(schema);
} }
const schema = this.groups[groupName]; const schema = this.groups[groupName];
// see if the group has this parameter. // see if the group has this parameter.
const existingProperty = values(schema.properties).first(each => each.language.default.name === parameter.language.default.name); const existingProperty = values(schema.properties).first(
(each) => each.language.default.name === parameter.language.default.name,
);
if (existingProperty) { if (existingProperty) {
// we have a property by this name one already // we have a property by this name one already
// mark the groupproperty with this parameter (so we can find it if needed) // mark the groupproperty with this parameter (so we can find it if needed)
existingProperty.originalParameter.push(parameter); existingProperty.originalParameter.push(parameter);
} else { } else {
// create a property for this parameter. // create a property for this parameter.
const gp = new GroupProperty(parameter.language.default.name, parameter.language.default.description, parameter.schema, { const gp = new GroupProperty(
required: parameter.required, parameter.language.default.name,
}); parameter.language.default.description,
parameter.schema,
{
required: parameter.required,
},
);
gp.originalParameter.push(parameter); gp.originalParameter.push(parameter);
schema.add(gp); schema.add(gp);
} }
// check if this groupSchema has been added as a parameter for this operation yet. // check if this groupSchema has been added as a parameter for this operation yet.
if (!addedGroupedParameters.has(schema)) { if (!addedGroupedParameters.has(schema)) {
addedGroupedParameters.set(schema, request.addParameter(new Parameter(camelCase(schema.language.default.name), schema.language.default.description, schema, { addedGroupedParameters.set(
implementation: ImplementationLocation.Method, 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. // make sure that it's not optional if any parameter are not optional.
@ -115,7 +149,7 @@ export class Grouper {
if (parameter.extensions) { if (parameter.extensions) {
delete parameter.extensions[xmsParameterGrouping]; delete parameter.extensions[xmsParameterGrouping];
if (length(parameter.extensions) === 0) { if (length(parameter.extensions) === 0) {
delete parameter['extensions']; delete parameter["extensions"];
} }
} }
} }
@ -123,6 +157,6 @@ export class Grouper {
} }
processResponseHeaders(operation: Operation) { 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen'; import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from '@azure-tools/autorest-extension-base'; import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel'; import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { Grouper } from './grouper'; import { Grouper } from "./grouper";
export async function processRequest(host: Host) { export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false; const debug = (await host.GetValue("debug")) || false;
try { try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema); const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {}); const options = <any>await session.getValue("modelerfour", {});
// process // process
const plugin = await new Grouper(session).init(); const plugin = await new Grouper(session).init();
@ -23,14 +22,13 @@ export async function processRequest(host: Host) {
const result = plugin.process(); const result = plugin.process();
// output the model to the pipeline // output the model to the pipeline
if (options['emit-yaml-tags'] !== false) { if (options["emit-yaml-tags"] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4'); host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
} }
if (options['emit-yaml-tags'] !== true) { if (options["emit-yaml-tags"] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags'); host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
} }
} catch (E) { } catch (E) {
if (debug) { if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`); console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);

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

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

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

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

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

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

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

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

@ -13,8 +13,8 @@
"scripts": { "scripts": {
"start": "node --max_old_space_size=4096 ./entrypoints/main.js", "start": "node --max_old_space_size=4096 ./entrypoints/main.js",
"debug": "node --max_old_space_size=4096 --inspect-brk=localhost:9229 ./entrypoints/main.js", "debug": "node --max_old_space_size=4096 --inspect-brk=localhost:9229 ./entrypoints/main.js",
"eslint-fix": "eslint . --fix --ext .ts", "fix": "eslint . --fix --ext .ts",
"eslint": "eslint . --ext .ts", "lint": "eslint . --ext .ts --max-warnings=0",
"static-link": "static-link --no-node-modules --debug", "static-link": "static-link --no-node-modules --debug",
"watch": "tsc -p . --watch", "watch": "tsc -p . --watch",
"build": "tsc -p .", "build": "tsc -p .",
@ -62,7 +62,9 @@
"@azure-tools/linq": "~3.1.0", "@azure-tools/linq": "~3.1.0",
"static-link": "^0.3.0", "static-link": "^0.3.0",
"chalk": "2.3.0", "chalk": "2.3.0",
"recursive-diff": "~1.0.6" "recursive-diff": "~1.0.6",
"prettier": "~2.2.1",
"eslint-plugin-prettier": "~3.2.0"
}, },
"static-link": { "static-link": {
"entrypoints": [], "entrypoints": [],

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

@ -3,17 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { serialize } from '@azure-tools/codegen'; import { serialize } from "@azure-tools/codegen";
import { Host, startSession } from '@azure-tools/autorest-extension-base'; import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { codeModelSchema, CodeModel } from '@azure-tools/codemodel'; import { codeModelSchema, CodeModel } from "@azure-tools/codemodel";
import { PreNamer } from './prenamer'; import { PreNamer } from "./prenamer";
export async function processRequest(host: Host) { export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false; const debug = (await host.GetValue("debug")) || false;
try { try {
const session = await startSession<CodeModel>(host, {}, codeModelSchema); const session = await startSession<CodeModel>(host, {}, codeModelSchema);
const options = <any>await session.getValue('modelerfour', {}); const options = <any>await session.getValue("modelerfour", {});
// process // process
const plugin = await new PreNamer(session).init(); const plugin = await new PreNamer(session).init();
@ -22,11 +22,11 @@ export async function processRequest(host: Host) {
const result = plugin.process(); const result = plugin.process();
// output the model to the pipeline // output the model to the pipeline
if (options['emit-yaml-tags'] !== false) { if (options["emit-yaml-tags"] !== false) {
host.WriteFile('code-model-v4.yaml', serialize(result, codeModelSchema), undefined, 'code-model-v4'); host.WriteFile("code-model-v4.yaml", serialize(result, codeModelSchema), undefined, "code-model-v4");
} }
if (options['emit-yaml-tags'] !== true) { if (options["emit-yaml-tags"] !== true) {
host.WriteFile('code-model-v4-no-tags.yaml', serialize(result), undefined, 'code-model-v4-no-tags'); host.WriteFile("code-model-v4-no-tags.yaml", serialize(result), undefined, "code-model-v4-no-tags");
} }
} catch (E) { } catch (E) {
if (debug) { 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 {
import { Session } from '@azure-tools/autorest-extension-base'; CodeModel,
import { values, length, Dictionary, when, items } from '@azure-tools/linq'; Parameter,
import { removeSequentialDuplicates, fixLeadingNumber, deconstruct, selectName, Style, Styler, pascalCase } from '@azure-tools/codegen'; 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>) { function getNameOptions(typeName: string, components: Array<string>) {
const result = new Set<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> // 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()]; return [...result.values()];
} }
function isUnassigned(value: string) { function isUnassigned(value: string) {
return !value || (value.indexOf('·') > -1); return !value || value.indexOf("·") > -1;
} }
interface SetNameOptions { 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 = { options = {
removeDuplicates: true, 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) { 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 = { options = {
removeDuplicates: true, 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, schema: Schema,
schemaNames: Set<string>, schemaNames: Set<string>,
session: Session<CodeModel>, session: Session<CodeModel>,
indexer: (schema: Schema, newName: string) => string = indexer: (schema: Schema, newName: string) => string = (schema: Schema, proposedName: string) => proposedName,
(schema: Schema, proposedName: string) => proposedName
): void { ): void {
const schemaName = schema.language.default.name; const schemaName = schema.language.default.name;
const maxDedupes = 1000; const maxDedupes = 1000;
if (schemaNames.has(indexer(schema, schemaName))) { if (schemaNames.has(indexer(schema, schemaName))) {
for (let i = 1; i <= maxDedupes; i++) { 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))) { if (!schemaNames.has(indexer(schema, newName))) {
schema.language.default.name = newName; schema.language.default.name = newName;
schemaNames.add(indexer(schema, 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 // We haven't seen the name before, add it
@ -81,7 +132,7 @@ function deduplicateSchemaName(
} }
export class PreNamer { export class PreNamer {
codeModel: CodeModel codeModel: CodeModel;
options: Dictionary<any> = {}; options: Dictionary<any> = {};
format = { format = {
parameter: Style.camel, parameter: Style.camel,
@ -97,20 +148,20 @@ export class PreNamer {
client: Style.pascal, client: Style.pascal,
local: Style.camel, local: Style.camel,
global: Style.pascal, global: Style.pascal,
override: <Dictionary<string>>{} override: <Dictionary<string>>{},
} };
enum = 0; enum = 0;
constant = 0; constant = 0;
constructor(protected session: Session<CodeModel>) { constructor(protected session: Session<CodeModel>) {
this.codeModel = session.model;// shadow(session.model, filename); this.codeModel = session.model; // shadow(session.model, filename);
} }
async init() { async init() {
// get our configuration for this run. // 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 naming = this.options.naming || {};
const maxPreserve = Number(naming['preserve-uppercase-max-length']) || 3; const maxPreserve = Number(naming["preserve-uppercase-max-length"]) || 3;
this.format = { this.format = {
parameter: Style.select(naming.parameter, Style.camel, maxPreserve), parameter: Style.select(naming.parameter, Style.camel, maxPreserve),
property: Style.select(naming.property, 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), type: Style.select(naming.type, Style.pascal, maxPreserve),
local: Style.select(naming.local, Style.camel, maxPreserve), local: Style.select(naming.local, Style.camel, maxPreserve),
global: Style.select(naming.global, Style.pascal, maxPreserve), global: Style.select(naming.global, Style.pascal, maxPreserve),
override: naming.override || {} override: naming.override || {},
} };
return this; return this;
} }
isUnassigned(value: string) { isUnassigned(value: string) {
return !value || (value.indexOf('·') > -1); return !value || value.indexOf("·") > -1;
} }
process() { process() {
if (this.options['prenamer'] === false) { if (this.options["prenamer"] === false) {
return this.codeModel; return this.codeModel;
} }
const deduplicateSchemaNames = const deduplicateSchemaNames =
!!this.options['lenient-model-deduplication'] || !!this.options["lenient-model-deduplication"] || !!this.options["resolve-schema-name-collisons"];
!!this.options['resolve-schema-name-collisons'];
// choice // choice
const choiceSchemaNames = new Set<string>(); const choiceSchemaNames = new Set<string>();
@ -153,7 +203,7 @@ export class PreNamer {
} }
for (const choice of values(schema.choices)) { 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)) { 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); setName(schema, this.format.type, schema.type, this.format.override);
if (isUnassigned(schema.language.default.description)) { 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 // dictionary
for (const schema of values(this.codeModel.schemas.dictionaries)) { 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)) { if (isUnassigned(schema.language.default.description)) {
schema.language.default.description = `Dictionary of ${schema.elementType.language.default.name}`; schema.language.default.description = `Dictionary of ${schema.elementType.language.default.name}`;
} }
@ -244,52 +299,54 @@ export class PreNamer {
const objectSchemaNames = new Set<string>(); const objectSchemaNames = new Set<string>();
for (const schema of values(this.codeModel.schemas.objects)) { 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) { if (deduplicateSchemaNames) {
deduplicateSchemaName( deduplicateSchemaName(
schema, schema,
objectSchemaNames, objectSchemaNames,
this.session, this.session,
(schema: Schema, proposedName: string) => (schema: Schema, proposedName: string) => `${schema.language.default.namespace || ""}.${proposedName}`,
`${(schema.language.default.namespace || "")}.${proposedName}`); );
} }
for (const property of values(schema.properties)) { 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>(); const groupSchemaNames = new Set<string>();
for (const schema of values(this.codeModel.schemas.groups)) { 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) { if (deduplicateSchemaNames) {
deduplicateSchemaName( deduplicateSchemaName(
schema, schema,
groupSchemaNames, groupSchemaNames,
this.session, this.session,
(schema: Schema, proposedName: string) => (schema: Schema, proposedName: string) => `${schema.language.default.namespace || ""}.${proposedName}`,
`${(schema.language.default.namespace || "")}.${proposedName}`); );
} }
for (const property of values(schema.properties)) { 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)) { for (const parameter of values(this.codeModel.globalParameters)) {
if (parameter.schema.type === SchemaType.Constant) { if (parameter.schema.type === SchemaType.Constant) {
setName(parameter, this.format.constantParameter, '', this.format.override); setName(parameter, this.format.constantParameter, "", this.format.override);
} else { } else {
setName(parameter, this.format.parameter, '', this.format.override); setName(parameter, this.format.parameter, "", this.format.override);
} }
} }
for (const operationGroup of this.codeModel.operationGroups) { 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) { 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); this.setParameterNames(operation);
for (const request of values(operation.requests)) { for (const request of values(operation.requests)) {
@ -326,23 +383,20 @@ export class PreNamer {
private setParameterNames(parameterContainer: Operation | Request) { private setParameterNames(parameterContainer: Operation | Request) {
for (const parameter of values(parameterContainer.signatureParameters)) { for (const parameter of values(parameterContainer.signatureParameters)) {
if (parameter.schema.type === SchemaType.Constant) { if (parameter.schema.type === SchemaType.Constant) {
setName(parameter, this.format.constantParameter, '', this.format.override); setName(parameter, this.format.constantParameter, "", this.format.override);
} } else {
else { setName(parameter, this.format.parameter, "", this.format.override);
setName(parameter, this.format.parameter, '', this.format.override);
} }
} }
for (const parameter of values(parameterContainer.parameters)) { for (const parameter of values(parameterContainer.parameters)) {
if ((parameterContainer.signatureParameters ?? []).indexOf(parameter) === -1) { if ((parameterContainer.signatureParameters ?? []).indexOf(parameter) === -1) {
if (parameter.schema.type === SchemaType.Constant) { if (parameter.schema.type === SchemaType.Constant) {
setName(parameter, this.format.constantParameter, '', this.format.override); setName(parameter, this.format.constantParameter, "", this.format.override);
} } else {
else {
if (parameter.implementation === ImplementationLocation.Client) { if (parameter.implementation === ImplementationLocation.Client) {
setName(parameter, this.format.global, '', this.format.override); setName(parameter, this.format.global, "", this.format.override);
} } else {
else { setName(parameter, this.format.local, "", this.format.override);
setName(parameter, this.format.local, '', this.format.override);
} }
} }
} }
@ -352,13 +406,13 @@ export class PreNamer {
private setResponseHeaderNames(response: Response) { private setResponseHeaderNames(response: Response) {
if (response.protocol.http) { if (response.protocol.http) {
for (const { value: header } of items(response.protocol.http.headers)) { 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() { 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)) { for (const request of values(operation.requests)) {
const parameters = values(operation.signatureParameters).concat(values(request.signatureParameters)); const parameters = values(operation.signatureParameters).concat(values(request.signatureParameters));
@ -380,21 +434,22 @@ export class PreNamer {
for (const parameter of collisions) { for (const parameter of collisions) {
let options = [parameter.language.default.name]; let options = [parameter.language.default.name];
if (isVirtualParameter(parameter)) { 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)); parameter.language.default.name = this.format.parameter(selectName(options, usedNames));
} }
} }
} }
} }
fixCollisions(schema: ObjectSchema) { 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); 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 inherited = [...getAllParentProperties(schema)];
const all = [...owned, ...inherited, ...flattened]; 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)) */) { 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)); const ct = inlined.get(this.format.property(each.language.default.name));
if (ct && ct > 1) { 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)); 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 { serialize } from "@azure-tools/codegen";
import { values, items, length, Dictionary, refCount, clone, keys } from '@azure-tools/linq'; import { Host, startSession } from "@azure-tools/autorest-extension-base";
import { Model as oai3, Refable, Dereferenced, dereference, Schema, PropertyDetails, JsonType, StringFormat } from '@azure-tools/openapi'; import { Interpretations } from "../modeler/interpretations";
import { serialize } from '@azure-tools/codegen'; import { getDiff } from "recursive-diff";
import { Host, startSession } from '@azure-tools/autorest-extension-base';
import { Interpretations } from '../modeler/interpretations';
import { getDiff } from 'recursive-diff'
export async function processRequest(host: Host) { export async function processRequest(host: Host) {
const debug = await host.GetValue('debug') || false; const debug = (await host.GetValue("debug")) || false;
try { try {
const session = await startSession<oai3>(host); const session = await startSession<oai3>(host);
@ -23,12 +31,22 @@ export async function processRequest(host: Host) {
const result = plugin.process(); const result = plugin.process();
// throw on errors. // throw on errors.
if (!await session.getValue('ignore-errors', false)) { if (!(await session.getValue("ignore-errors", false))) {
session.checkpoint(); session.checkpoint();
} }
host.WriteFile('prechecked-openapi-document.yaml', serialize(result, { sortKeys: false }), undefined, 'prechecked-openapi-document'); host.WriteFile(
host.WriteFile('original-openapi-document.yaml', serialize(input, { sortKeys: false }), undefined, 'openapi-document'); "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) { } catch (E) {
if (debug) { if (debug) {
console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`); console.error(`${__filename} - FAILURE ${JSON.stringify(E)} ${E.stack}`);
@ -43,14 +61,14 @@ export class QualityPreChecker {
protected interpret: Interpretations; protected interpret: Interpretations;
constructor(protected session: Session<oai3>) { 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); this.interpret = new Interpretations(session);
} }
async init() { async init() {
// get our configuration for this run. // get our configuration for this run.
this.options = await this.session.getValue('modelerfour', {}); this.options = await this.session.getValue("modelerfour", {});
return this; return this;
} }
@ -59,35 +77,37 @@ export class QualityPreChecker {
return dereference(this.input, item); return dereference(this.input, item);
} }
getProperties(schema: Schema) { getProperties(schema: Schema) {
return items(schema.properties).select(each => ({ return items(schema.properties).select((each) => ({
key: each.key, key: each.key,
name: <string>this.interpret.getPreferredName(each.value, 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); //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 }> { getSchemasFromArray(
return values(schemas).select(a => { 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); const { instance: schema, name } = this.resolve(a);
return { return {
name: this.interpret.getName(name, schema), name: this.interpret.getName(name, schema),
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)) { for (const parent of this.getSchemasFromArray(tag, schema.allOf)) {
yield parent; yield parent;
yield* this.getAllParents(parent.name, parent.schema); 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)) { for (const parent of this.getSchemasFromArray(tag, schema.allOf)) {
yield* this.getAllParents(parent.name, parent.schema); yield* this.getAllParents(parent.name, parent.schema);
} }
@ -99,40 +119,51 @@ export class QualityPreChecker {
} }
completed.add(schema); completed.add(schema);
if (schema.allOf && schema.properties) { if (schema.allOf && schema.properties) {
const myProperties = this.getProperties(schema).toArray(); const myProperties = this.getProperties(schema).toArray();
for (const { name: parentName, schema: parentSchema } of this.getAllParents(schemaName, schema)) { for (const { name: parentName, schema: parentSchema } of this.getAllParents(schemaName, schema)) {
this.checkForHiddenProperties(parentName, parentSchema, completed); this.checkForHiddenProperties(parentName, parentSchema, completed);
for (const { key, name: propName, property: parentProp } of this.getProperties(parentSchema)) { 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) { if (myProp) {
// check if the only thing different is the description. // 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) { 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. // 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]; delete schema.properties[myProp.key];
continue; continue;
} }
if (diff.length === 1) { if (diff.length === 1) {
// special case to yell about readonly changes // special case to yell about readonly changes
if (diff[0].path[0] === 'readOnly') { 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']); 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]; delete schema.properties[myProp.key];
continue; continue;
} }
} }
if (diff.length > 0) { if (diff.length > 0) {
const details = diff.map(each => `${each.path.join('.')} => '${each.op === 'delete' ? '<removed>' : each.val}'`).join(','); const details = diff
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']); .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; continue;
} }
} }
@ -148,13 +179,15 @@ export class QualityPreChecker {
completed.add(schema); completed.add(schema);
if (schema.allOf) { if (schema.allOf) {
const grandParents = [...this.getGrandParents(schemaName, schema)]; const grandParents = [...this.getGrandParents(schemaName, schema)];
const direct = [...this.getSchemasFromArray(schemaName, schema.allOf)]; const direct = [...this.getSchemasFromArray(schemaName, schema.allOf)];
for (const myParent of direct) { for (const myParent of direct) {
for (const duplicate of grandParents.filter(each => each.schema === myParent.schema)) { 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']); 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; return false;
case JsonType.String: case JsonType.String:
return schema.enum || schema['x-ms-enum']; return schema.enum || schema["x-ms-enum"];
case JsonType.Object: case JsonType.Object:
// empty objects don't worry. // empty objects don't worry.
@ -178,30 +211,33 @@ export class QualityPreChecker {
return true; return true;
default: 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 { 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 // Returns true if scanning should be restarted
const innerCheckForDuplicateSchemas = (): any => { const innerCheckForDuplicateSchemas = (): any => {
const errors = new Set<string>(); const errors = new Set<string>();
if (this.input.components && this.input.components.schemas) { if (this.input.components && this.input.components.schemas) {
const dupedNames = items(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( .groupBy(
// Make sure to check x-ms-client-name first to see if the schema is already being renamed // 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.value.instance["x-ms-client-name"] || each.value.instance["x-ms-metadata"]?.name,
each => each); (each) => each,
);
for (const [name, schemas] of dupedNames.entries()) { for (const [name, schemas] of dupedNames.entries()) {
if (name && schemas.length > 1) { 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) { if (diff.length === 0) {
// found two schemas that are indeed the same. // found two schemas that are indeed the same.
@ -210,42 +246,67 @@ export class QualityPreChecker {
delete this.input.components.schemas[schemas[1].key]; delete this.input.components.schemas[schemas[1].key];
const text = JSON.stringify(this.input); 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 // update metadata to match
if (this.input?.components?.schemas?.[schemas[0].key]) { if (this.input?.components?.schemas?.[schemas[0].key]) {
const primarySchema = this.resolve(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 primaryMetadata = primarySchema.instance['x-ms-metadata']; const secondaryMetadata = schemas[1].value.instance["x-ms-metadata"];
const secondaryMetadata = schemas[1].value.instance['x-ms-metadata'];
if (primaryMetadata && secondaryMetadata) { if (primaryMetadata && secondaryMetadata) {
primaryMetadata.apiVersions = [...new Set<string>([...primaryMetadata.apiVersions || [], ...secondaryMetadata.apiVersions || []])] primaryMetadata.apiVersions = [
primaryMetadata.filename = [...new Set<string>([...primaryMetadata.filename || [], ...secondaryMetadata.filename || []])] ...new Set<string>([
primaryMetadata.originalLocations = [...new Set<string>([...primaryMetadata.originalLocations || [], ...secondaryMetadata.originalLocations || []])] ...(primaryMetadata.apiVersions || []),
primaryMetadata['x-ms-secondary-file'] = !(!primaryMetadata['x-ms-secondary-file'] || !secondaryMetadata['x-ms-secondary-file']) ...(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 // Restart the scan now that the duplicate has been removed
return true; return true;
} }
// it may not be identical, but if it's not an object, I'm not sure we care too much. // 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))) { 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'); 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) { if (diff.length > 0) {
const details = diff.map(each => { const details = diff
const path = each.path.join('.'); .map((each) => {
let iValue = each.op === 'add' ? '<none>' : JSON.stringify(each.oldVal); const path = each.path.join(".");
if (each.op !== 'update') { let iValue = each.op === "add" ? "<none>" : JSON.stringify(each.oldVal);
const v = rdiff.find(each => each.path.join('.') === path) if (each.op !== "update") {
iValue = JSON.stringify(v?.val); 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}`; const nValue = each.op === "delete" ? "<none>" : JSON.stringify(each.val);
}).join(','); return `${path}: ${iValue} => ${nValue}`;
})
.join(",");
errors.add(`Duplicate Schema named ${name} -- ${details} `); errors.add(`Duplicate Schema named ${name} -- ${details} `);
continue; continue;
} }
@ -256,15 +317,18 @@ export class QualityPreChecker {
for (const each of errors) { for (const each of errors) {
// Allow duplicate schemas if requested // Allow duplicate schemas if requested
if (!!this.options["lenient-model-deduplication"]) { if (this.options["lenient-model-deduplication"]) {
this.session.warning(each, ['PreCheck', 'DuplicateSchema']); this.session.warning(each, ["PreCheck", "DuplicateSchema"]);
} else { } 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()) { while (innerCheckForDuplicateSchemas()) {
// Loop until the scan is complete // Loop until the scan is complete
} }
@ -274,7 +338,10 @@ export class QualityPreChecker {
fixUpSchemasThatUseAllOfInsteadOfJustRef() { fixUpSchemasThatUseAllOfInsteadOfJustRef() {
const schemas = this.input.components?.schemas; const schemas = this.input.components?.schemas;
if (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 // we're looking for schemas that offer no possible value
// because they just use allOf instead of $ref // because they just use allOf instead of $ref
if (!schema.type || schema.type === JsonType.Object) { if (!schema.type || schema.type === JsonType.Object) {
@ -289,15 +356,26 @@ export class QualityPreChecker {
const $ref = schema?.allOf?.[0]?.$ref; const $ref = schema?.allOf?.[0]?.$ref;
const text = JSON.stringify(this.input); const text = JSON.stringify(this.input);
this.input = JSON.parse(text.replace(new RegExp(`"\\#\\/components\\/schemas\\/${key}"`, 'g'), `"${$ref}"`)); this.input = JSON.parse(
const location = schema['x-ms-metadata'].originalLocations[0].replace(/^.*\//, '') text.replace(new RegExp(`"\\#\\/components\\/schemas\\/${key}"`, "g"), `"${$ref}"`),
if (schema['x-internal-autorest-anonymous-schema']) { );
const location = schema["x-ms-metadata"].originalLocations[0].replace(/^.*\//, "");
if (schema["x-internal-autorest-anonymous-schema"]) {
delete schemas[key]; 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 { } else {
// NOTE: Disabled removing of non-anonymous schema for now until // NOTE: Disabled removing of non-anonymous schema for now until
// it has been discussed in Azure/autorest.modelerfour#278 // 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() { fixUpObjectsWithoutType() {
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) =>
if (<any>schema.type === 'file' || <any>schema.format === 'file' || <any>schema.format === 'binary') { this.resolve(s),
)) {
if (<any>schema.type === "file" || <any>schema.format === "file" || <any>schema.format === "binary") {
// handle inconsistency in file format handling. // handle inconsistency in file format handling.
this.session.hint( 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.`, `'The schema ${schema?.["x-ms-metadata"]?.name || name} with 'type: ${schema.type}', format: ${
['PreCheck', 'BinarySchema'], schema); schema.format
}' will be treated as a binary blob for binary media types.`,
["PreCheck", "BinarySchema"],
schema,
);
schema.type = JsonType.String; schema.type = JsonType.String;
schema.format = StringFormat.Binary; 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 // 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. // 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; schema.type = JsonType.Object;
break; break;
} }
if (schema.additionalProperties) { if (schema.additionalProperties) {
// this looks like it's going to be a dictionary // this looks like it's going to be a dictionary
// we'll mark it as object and let the processObjectSchema sort it out. // 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; schema.type = JsonType.Object;
break; break;
} }
@ -338,7 +434,13 @@ export class QualityPreChecker {
if (schema.allOf || schema.anyOf || schema.oneOf) { if (schema.allOf || schema.anyOf || schema.oneOf) {
// if the model has properties, then we're going to assume they meant to say JsonType.object // 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. // 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; schema.type = JsonType.Object;
break; break;
} }
@ -348,9 +450,7 @@ export class QualityPreChecker {
} }
isEmptyObjectSchema(schema: Schema): boolean { isEmptyObjectSchema(schema: Schema): boolean {
if (length(schema.properties) > 0 || if (length(schema.properties) > 0 || length(schema.allOf) > 0 || schema.additionalProperties === true) {
length(schema.allOf) > 0 ||
schema.additionalProperties === true) {
return false; return false;
} }
@ -365,15 +465,21 @@ export class QualityPreChecker {
fixUpSchemasWithEmptyObjectParent() { fixUpSchemasWithEmptyObjectParent() {
const schemas = this.input.components?.schemas; const schemas = this.input.components?.schemas;
if (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 (schema.type === JsonType.Object) {
if (length(schema.allOf) > 1) { if (length(schema.allOf) > 1) {
const schemaName = schema['x-ms-metadata']?.name || name; const schemaName = schema["x-ms-metadata"]?.name || name;
schema.allOf = schema.allOf?.filter(p => { schema.allOf = schema.allOf?.filter((p) => {
const parent = this.resolve(p).instance; const parent = this.resolve(p).instance;
if (this.isEmptyObjectSchema(parent)) { 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; return false;
} }
@ -385,10 +491,8 @@ export class QualityPreChecker {
} }
} }
process() { process() {
this.fixUpSchemasThatUseAllOfInsteadOfJustRef();
this.fixUpSchemasThatUseAllOfInsteadOfJustRef()
this.fixUpObjectsWithoutType(); this.fixUpObjectsWithoutType();
@ -397,17 +501,19 @@ export class QualityPreChecker {
this.checkForDuplicateSchemas(); this.checkForDuplicateSchemas();
let onlyOnce = new WeakSet<Schema>(); 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); this.checkForHiddenProperties(this.interpret.getName(name, schema), schema, onlyOnce);
} }
onlyOnce = new WeakSet<Schema>(); 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); this.checkForDuplicateParents(this.interpret.getName(name, schema), schema, onlyOnce);
} }
return this.input; return this.input;
} }
} }

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

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

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

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

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

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

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

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

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

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

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

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

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