mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
Chore/configure eslint (#808)
This commit is contained in:
29
.eslintrc.yml
Normal file
29
.eslintrc.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
env:
|
||||
browser: true
|
||||
es2021: true
|
||||
node: true
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:@typescript-eslint/recommended
|
||||
overrides: []
|
||||
parser: '@typescript-eslint/parser'
|
||||
parserOptions:
|
||||
ecmaVersion: latest
|
||||
sourceType: module
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
rules:
|
||||
'@typescript-eslint/no-unused-vars':
|
||||
- error
|
||||
- argsIgnorePattern: ^_
|
||||
varsIgnorePattern: ^_
|
||||
'@typescript-eslint/no-var-requires': off
|
||||
'@typescript-eslint/no-empty-function': off
|
||||
'@typescript-eslint/no-inferrable-types': off
|
||||
'@typescript-eslint/ban-types': warn
|
||||
no-prototype-builtins: off
|
||||
no-useless-escape: warn
|
||||
ignorePatterns:
|
||||
- '**/__tests__/'
|
||||
- 'packages/*/lib/'
|
||||
- '*.js'
|
||||
22
.github/workflows/tests.yaml
vendored
22
.github/workflows/tests.yaml
vendored
@@ -101,4 +101,26 @@ jobs:
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
CI: true
|
||||
- run: npm run prettier:check
|
||||
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
CI: true
|
||||
- run: npm run eslint
|
||||
|
||||
@@ -14,7 +14,7 @@ module.exports = {
|
||||
statements: 79,
|
||||
branches: 71,
|
||||
functions: 68,
|
||||
lines: 78,
|
||||
lines: 79,
|
||||
},
|
||||
'packages/cli/': {
|
||||
statements: 37,
|
||||
|
||||
1720
package-lock.json
generated
1720
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@
|
||||
"e2e": "npm run webpack-bundle -- --mode=none && jest --roots=./__tests__/",
|
||||
"prettier": "npx prettier --write \"**/*.{ts,js}\"",
|
||||
"prettier:check": "npx prettier --check \"**/*.{ts,js}\"",
|
||||
"eslint": "eslint packages/**",
|
||||
"clean": "rm -rf packages/**/lib packages/**/node_modules packages/**/*.tsbuildinfo package-lock.json node_modules",
|
||||
"watch": "tsc -b tsconfig.build.json --watch ",
|
||||
"compile": "tsc -b tsconfig.build.json",
|
||||
@@ -57,6 +58,9 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^17.0.31",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"eslint": "^8.22.0",
|
||||
"jest": "^26.6.3",
|
||||
"null-loader": "^4.0.0",
|
||||
"outdent": "^0.7.1",
|
||||
|
||||
@@ -131,8 +131,8 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
||||
}
|
||||
}
|
||||
|
||||
let joinedDef: any = {};
|
||||
let potentialConflicts = {
|
||||
const joinedDef: any = {};
|
||||
const potentialConflicts = {
|
||||
tags: {},
|
||||
paths: {},
|
||||
components: {},
|
||||
@@ -343,9 +343,7 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
||||
if (!potentialConflicts.paths.hasOwnProperty(path)) {
|
||||
potentialConflicts.paths[path] = {};
|
||||
}
|
||||
for (const operation of Object.keys(paths[path])) {
|
||||
// @ts-ignore
|
||||
const pathOperation = paths[path][operation];
|
||||
for (const [operation, pathOperation] of Object.entries(paths[path])) {
|
||||
joinedDef.paths[path][operation] = pathOperation;
|
||||
potentialConflicts.paths[path][operation] = [
|
||||
...(potentialConflicts.paths[path][operation] || []),
|
||||
@@ -361,7 +359,7 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
||||
api,
|
||||
];
|
||||
}
|
||||
let { tags, security } = joinedDef.paths[path][operation];
|
||||
const { tags, security } = joinedDef.paths[path][operation];
|
||||
if (tags) {
|
||||
joinedDef.paths[path][operation].tags = tags.map((tag: string) =>
|
||||
addPrefix(tag, tagsPrefix)
|
||||
@@ -412,13 +410,11 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
||||
if (!joinedDef.hasOwnProperty(COMPONENTS)) {
|
||||
joinedDef[COMPONENTS] = {};
|
||||
}
|
||||
for (const component of Object.keys(components)) {
|
||||
for (const [component, componentObj] of Object.entries(components)) {
|
||||
if (!potentialConflicts[COMPONENTS].hasOwnProperty(component)) {
|
||||
potentialConflicts[COMPONENTS][component] = {};
|
||||
joinedDef[COMPONENTS][component] = {};
|
||||
}
|
||||
// @ts-ignore
|
||||
const componentObj = components[component];
|
||||
for (const item of Object.keys(componentObj)) {
|
||||
const componentPrefix = addPrefix(item, componentsPrefix!);
|
||||
potentialConflicts.components[component][componentPrefix] = [
|
||||
@@ -436,7 +432,6 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
||||
{ apiFilename, api, potentialConflicts, tagsPrefix, componentsPrefix }: JoinDocumentContext
|
||||
) {
|
||||
const xWebhooks = 'x-webhooks';
|
||||
// @ts-ignore
|
||||
const openapiXWebhooks = openapi[xWebhooks];
|
||||
if (openapiXWebhooks) {
|
||||
if (!joinedDef.hasOwnProperty(xWebhooks)) {
|
||||
@@ -455,7 +450,7 @@ export async function handleJoin(argv: JoinArgv, packageVersion: string) {
|
||||
];
|
||||
}
|
||||
for (const operationKey of Object.keys(joinedDef[xWebhooks][webhook])) {
|
||||
let { tags } = joinedDef[xWebhooks][webhook][operationKey];
|
||||
const { tags } = joinedDef[xWebhooks][webhook][operationKey];
|
||||
if (tags) {
|
||||
joinedDef[xWebhooks][webhook][operationKey].tags = tags.map((tag: string) =>
|
||||
addPrefix(tag, tagsPrefix)
|
||||
@@ -498,7 +493,7 @@ function doesComponentsDiffer(curr: object, next: object) {
|
||||
function validateComponentsDifference(files: any) {
|
||||
let isDiffer = false;
|
||||
for (let i = 0, len = files.length; i < len; i++) {
|
||||
let next = files[i + 1];
|
||||
const next = files[i + 1];
|
||||
if (next && doesComponentsDiffer(files[i], next)) {
|
||||
isDiffer = true;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function handleLint(argv: LintOptions, version: string) {
|
||||
totals.ignored += fileTotals.ignored;
|
||||
|
||||
if (argv['generate-ignore-file']) {
|
||||
for (let m of results) {
|
||||
for (const m of results) {
|
||||
config.styleguide.addIgnore(m);
|
||||
totalIgnored++;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function previewDocs(
|
||||
force?: boolean;
|
||||
} & Omit<Skips, 'skip-rule'>
|
||||
) {
|
||||
let isAuthorizedWithRedocly: boolean = false;
|
||||
let isAuthorizedWithRedocly = false;
|
||||
let redocOptions: any = {};
|
||||
let config = await reloadConfig();
|
||||
|
||||
@@ -129,7 +129,7 @@ export async function previewDocs(
|
||||
});
|
||||
|
||||
async function reloadConfig() {
|
||||
let config = await loadConfig(argv.config);
|
||||
const config = await loadConfig(argv.config);
|
||||
const redoclyClient = new RedoclyClient();
|
||||
isAuthorizedWithRedocly = await redoclyClient.isAuthorizedWithRedocly();
|
||||
const resolvedConfig = getMergedConfig(config, argv.api);
|
||||
@@ -152,7 +152,9 @@ export function debounce(func: Function, wait: number, immediate?: boolean) {
|
||||
let timeout: NodeJS.Timeout | null;
|
||||
|
||||
return function executedFunction(...args: any[]) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const context = this;
|
||||
|
||||
const later = () => {
|
||||
|
||||
@@ -99,6 +99,7 @@ export default async function startPreviewServer(
|
||||
}
|
||||
} else {
|
||||
let filePath =
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
{
|
||||
'/hot.js': path.join(__dirname, 'hot.js'),
|
||||
@@ -142,7 +143,7 @@ export default async function startPreviewServer(
|
||||
console.timeEnd(colorette.dim(`GET ${request.url}`));
|
||||
};
|
||||
|
||||
let wsPort = await portfinder.getPortPromise({ port: 32201 });
|
||||
const wsPort = await portfinder.getPortPromise({ port: 32201 });
|
||||
|
||||
const server = startHttpServer(port, host, handler);
|
||||
server.on('listening', () => {
|
||||
|
||||
@@ -119,7 +119,7 @@ export async function handlePush(argv: PushArgs): Promise<void> {
|
||||
|
||||
let uploaded = 0;
|
||||
|
||||
for (let file of filesToUpload.files) {
|
||||
for (const file of filesToUpload.files) {
|
||||
const { signedUploadUrl, filePath } = await client.registryApi.prepareFileUpload({
|
||||
organizationId,
|
||||
name,
|
||||
@@ -206,7 +206,7 @@ function getFilesList(dir: string, files?: any): string[] {
|
||||
}
|
||||
|
||||
async function collectFilesToUpload(api: string, config: Config) {
|
||||
let files: { filePath: string; keyOnS3: string; contents?: Buffer }[] = [];
|
||||
const files: { filePath: string; keyOnS3: string; contents?: Buffer }[] = [];
|
||||
const [{ path: apiPath }] = await getFallbackApisOrExit([api], config);
|
||||
|
||||
process.stdout.write('Bundling definition\n');
|
||||
@@ -246,7 +246,7 @@ async function collectFilesToUpload(api: string, config: Config) {
|
||||
const fileList = getFilesList(dir, []);
|
||||
files.push(...fileList.map((f) => getFileEntry(f)));
|
||||
}
|
||||
let pluginFiles = new Set<string>();
|
||||
const pluginFiles = new Set<string>();
|
||||
for (const plugin of config.styleguide.pluginPaths) {
|
||||
if (typeof plugin !== 'string') continue;
|
||||
const fileList = getFilesList(getFolder(plugin), []);
|
||||
@@ -282,7 +282,7 @@ function getFolder(filePath: string) {
|
||||
}
|
||||
|
||||
function hashFiles(filePaths: { filePath: string }[]) {
|
||||
let sum = createHash('sha256');
|
||||
const sum = createHash('sha256');
|
||||
filePaths.forEach((file) => sum.update(fs.readFileSync(file.filePath)));
|
||||
return sum.digest('hex');
|
||||
}
|
||||
@@ -318,7 +318,7 @@ type BarePushArgs = Omit<PushArgs, 'api' | 'destination' | 'branchName'> & {
|
||||
export const transformPush =
|
||||
(callback: typeof handlePush) =>
|
||||
({ maybeApiOrDestination, maybeDestination, maybeBranchName, branch, ...rest }: BarePushArgs) => {
|
||||
if (!!maybeBranchName) {
|
||||
if (maybeBranchName) {
|
||||
process.stderr.write(
|
||||
yellow(
|
||||
'Deprecation warning: Do not use the third parameter as a branch name. Please use a separate --branch option instead.'
|
||||
@@ -353,7 +353,7 @@ function uploadFileToS3(url: string, filePathOrBuffer: string | Buffer) {
|
||||
typeof filePathOrBuffer === 'string'
|
||||
? fs.statSync(filePathOrBuffer).size
|
||||
: filePathOrBuffer.byteLength;
|
||||
let readStream =
|
||||
const readStream =
|
||||
typeof filePathOrBuffer === 'string' ? fs.createReadStream(filePathOrBuffer) : filePathOrBuffer;
|
||||
|
||||
return fetch(url, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { red, blue, yellow, green } from 'colorette';
|
||||
import * as fs from 'fs';
|
||||
import { parseYaml, slash, isRef } from '@redocly/openapi-core';
|
||||
import { parseYaml, slash, isRef, isTruthy } from '@redocly/openapi-core';
|
||||
import type { OasRef } from '@redocly/openapi-core';
|
||||
import * as path from 'path';
|
||||
import { performance } from 'perf_hooks';
|
||||
const isEqual = require('lodash.isequal');
|
||||
@@ -267,11 +268,11 @@ function gatherComponentsFiles(
|
||||
componentName: string,
|
||||
filename: string
|
||||
) {
|
||||
let inherits = [];
|
||||
let inherits: string[] = [];
|
||||
if (componentType === OPENAPI3_COMPONENT.Schemas) {
|
||||
inherits = ((components?.[componentType]?.[componentName] as Oas3Schema)?.allOf || [])
|
||||
.map((s: any) => s.$ref)
|
||||
.filter(Boolean);
|
||||
.map(({ $ref }) => $ref)
|
||||
.filter(isTruthy);
|
||||
}
|
||||
componentsFiles[componentType] = componentsFiles[componentType] || {};
|
||||
componentsFiles[componentType][componentName] = { inherits, filename };
|
||||
@@ -301,7 +302,7 @@ function iteratePathItems(
|
||||
continue;
|
||||
}
|
||||
for (const sample of methodDataXCode) {
|
||||
if (sample.source && (sample.source as any).$ref) continue;
|
||||
if (sample.source && (sample.source as unknown as OasRef).$ref) continue;
|
||||
const sampleFileName = path.join(
|
||||
openapiDir,
|
||||
'code_samples',
|
||||
@@ -312,6 +313,7 @@ function iteratePathItems(
|
||||
|
||||
fs.mkdirSync(path.dirname(sampleFileName), { recursive: true });
|
||||
fs.writeFileSync(sampleFileName, sample.source);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
sample.source = {
|
||||
$ref: slash(path.relative(outDir, sampleFileName)),
|
||||
@@ -340,6 +342,7 @@ function iterateComponents(
|
||||
componentTypes.forEach(iterateAndGatherComponentsFiles);
|
||||
componentTypes.forEach(iterateComponentTypes);
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) {
|
||||
const componentDirPath = path.join(componentsDir, componentType);
|
||||
for (const componentName of Object.keys(components?.[componentType] || {})) {
|
||||
@@ -348,6 +351,7 @@ function iterateComponents(
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function iterateComponentTypes(componentType: Oas3ComponentName) {
|
||||
const componentDirPath = path.join(componentsDir, componentType);
|
||||
createComponentDir(componentDirPath, componentType);
|
||||
|
||||
@@ -177,7 +177,6 @@ export function handleError(e: Error, ref: string) {
|
||||
process.stderr.write(`Failed to parse api definition at ${ref}:\n\n - ${e.message}.\n\n`);
|
||||
// TODO: codeframe
|
||||
} else {
|
||||
// @ts-ignore
|
||||
if (e instanceof CircularJSONNotSupportedError) {
|
||||
process.stderr.write(
|
||||
red(`Detected circular reference which can't be converted to JSON.\n`) +
|
||||
|
||||
@@ -15,7 +15,6 @@ const rebillyDocument = parseYamlToDocument(
|
||||
const config = makeConfigForRuleset({
|
||||
test: () => {
|
||||
return {
|
||||
// @ts-ignore
|
||||
Schema(schema, ctx) {
|
||||
if (schema.type === 'number') {
|
||||
ctx.report({
|
||||
|
||||
@@ -10,7 +10,7 @@ import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types';
|
||||
import { isAbsoluteUrl, isRef, Location, refBaseName } from './ref-utils';
|
||||
import { initRules } from './config/rules';
|
||||
import { reportUnresolvedRef } from './rules/no-unresolved-refs';
|
||||
import { isPlainObject } from './utils';
|
||||
import { isPlainObject, isTruthy } from './utils';
|
||||
import { OasRef } from './typings/openapi';
|
||||
import { isRedoclyRegistryURL } from './redocly';
|
||||
import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './rules/oas2/remove-unused-components';
|
||||
@@ -302,6 +302,8 @@ function makeBundleVisitor(
|
||||
if (!isPlainObject(resolved.node)) {
|
||||
ctx.parent[ctx.key] = resolved.node;
|
||||
} else {
|
||||
// TODO: why $ref isn't optional in OasRef?
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
delete ref.$ref;
|
||||
Object.assign(ref, resolved.node);
|
||||
@@ -348,7 +350,7 @@ function makeBundleVisitor(
|
||||
|
||||
let name = '';
|
||||
|
||||
const refParts = pointer.slice(2).split('/').filter(Boolean); // slice(2) removes "#/"
|
||||
const refParts = pointer.slice(2).split('/').filter(isTruthy); // slice(2) removes "#/"
|
||||
while (refParts.length > 0) {
|
||||
name = refParts.pop() + (name ? `-${name}` : '');
|
||||
if (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OasVersion } from '../../oas-types';
|
||||
import { Config, StyleguideConfig } from '../config';
|
||||
import { getMergedConfig } from '../utils';
|
||||
|
||||
@@ -242,3 +243,37 @@ describe('getMergedConfig', () => {
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('StyleguideConfig.extendTypes', () => {
|
||||
let oas3 = jest.fn();
|
||||
let oas2 = jest.fn();
|
||||
let testRawConfigStyleguide = {
|
||||
plugins: [
|
||||
{
|
||||
id: 'test-types-plugin',
|
||||
typeExtension: {
|
||||
oas3,
|
||||
oas2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
it('should call only oas3 types extension', () => {
|
||||
const styleguideConfig = new StyleguideConfig(testRawConfigStyleguide);
|
||||
styleguideConfig.extendTypes({}, OasVersion.Version3_0);
|
||||
expect(oas3).toHaveBeenCalledTimes(1);
|
||||
expect(oas2).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
it('should call only oas2 types extension', () => {
|
||||
const styleguideConfig = new StyleguideConfig(testRawConfigStyleguide);
|
||||
styleguideConfig.extendTypes({}, OasVersion.Version2);
|
||||
expect(oas3).toHaveBeenCalledTimes(0);
|
||||
expect(oas2).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('should throw error if for oas version different from 2 and 3', () => {
|
||||
const styleguideConfig = new StyleguideConfig(testRawConfigStyleguide);
|
||||
expect(() => styleguideConfig.extendTypes({}, 'something else' as OasVersion)).toThrowError(
|
||||
'Not implemented'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,6 +71,7 @@ export function resolvePlugins(
|
||||
): Plugin[] {
|
||||
if (!plugins) return [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
|
||||
|
||||
@@ -166,7 +167,7 @@ export async function resolveApis({
|
||||
resolver?: BaseResolver;
|
||||
}): Promise<Record<string, ResolvedApi>> {
|
||||
const { apis = {}, styleguide: styleguideConfig = {} } = rawConfig;
|
||||
let resolvedApis: Record<string, ResolvedApi> = {};
|
||||
const resolvedApis: Record<string, ResolvedApi> = {};
|
||||
for (const [apiName, apiContent] of Object.entries(apis || {})) {
|
||||
if (apiContent.styleguide?.extends?.some(isNotString)) {
|
||||
throw new Error(
|
||||
@@ -287,7 +288,7 @@ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedSt
|
||||
throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
|
||||
}
|
||||
|
||||
const preset = plugin.configs?.[configName]! as ResolvedStyleguideConfig;
|
||||
const preset = plugin.configs?.[configName];
|
||||
if (!preset) {
|
||||
throw new Error(
|
||||
pluginId
|
||||
|
||||
@@ -172,9 +172,11 @@ export class StyleguideConfig {
|
||||
case OasVersion.Version3_1:
|
||||
if (!plugin.typeExtension.oas3) continue;
|
||||
extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
|
||||
break;
|
||||
case OasVersion.Version2:
|
||||
if (!plugin.typeExtension.oas2) continue;
|
||||
extendedTypes = plugin.typeExtension.oas2(extendedTypes, version);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
@@ -192,7 +194,7 @@ export class StyleguideConfig {
|
||||
severity: settings,
|
||||
};
|
||||
} else {
|
||||
return { severity: 'error' as 'error', ...settings };
|
||||
return { severity: 'error', ...settings };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,10 +205,10 @@ export class StyleguideConfig {
|
||||
const settings = this.preprocessors[oasVersion][ruleId] || 'off';
|
||||
if (typeof settings === 'string') {
|
||||
return {
|
||||
severity: settings === 'on' ? ('error' as 'error') : settings,
|
||||
severity: settings === 'on' ? 'error' : settings,
|
||||
};
|
||||
} else {
|
||||
return { severity: 'error' as 'error', ...settings };
|
||||
return { severity: 'error', ...settings };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,10 +218,10 @@ export class StyleguideConfig {
|
||||
const settings = this.decorators[oasVersion][ruleId] || 'off';
|
||||
if (typeof settings === 'string') {
|
||||
return {
|
||||
severity: settings === 'on' ? ('error' as 'error') : settings,
|
||||
severity: settings === 'on' ? 'error' : settings,
|
||||
};
|
||||
} else {
|
||||
return { severity: 'error' as 'error', ...settings };
|
||||
return { severity: 'error', ...settings };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,12 +252,14 @@ export class StyleguideConfig {
|
||||
getRulesForOasVersion(version: OasMajorVersion) {
|
||||
switch (version) {
|
||||
case OasMajorVersion.Version3:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const oas3Rules: Oas3RuleSet[] = []; // default ruleset
|
||||
this.plugins.forEach((p) => p.preprocessors?.oas3 && oas3Rules.push(p.preprocessors.oas3));
|
||||
this.plugins.forEach((p) => p.rules?.oas3 && oas3Rules.push(p.rules.oas3));
|
||||
this.plugins.forEach((p) => p.decorators?.oas3 && oas3Rules.push(p.decorators.oas3));
|
||||
return oas3Rules;
|
||||
case OasMajorVersion.Version2:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const oas2Rules: Oas2RuleSet[] = []; // default ruleset
|
||||
this.plugins.forEach((p) => p.preprocessors?.oas2 && oas2Rules.push(p.preprocessors.oas2));
|
||||
this.plugins.forEach((p) => p.rules?.oas2 && oas2Rules.push(p.rules.oas2));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { yellow } from 'colorette';
|
||||
import {
|
||||
assignExisting,
|
||||
isTruthy,
|
||||
showErrorForDeprecatedField,
|
||||
showWarningForDeprecatedField,
|
||||
} from '../utils';
|
||||
@@ -83,7 +84,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
|
||||
extendPaths: [],
|
||||
};
|
||||
|
||||
for (let rulesConf of rulesConfList) {
|
||||
for (const rulesConf of rulesConfList) {
|
||||
if (rulesConf.extends) {
|
||||
throw new Error(
|
||||
`'extends' is not supported in shared configs yet: ${JSON.stringify(rulesConf, null, 2)}.`
|
||||
@@ -128,14 +129,14 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
|
||||
config.rawConfig?.styleguide?.extendPaths,
|
||||
]
|
||||
.flat()
|
||||
.filter(Boolean) as string[];
|
||||
.filter(isTruthy);
|
||||
|
||||
const pluginPaths = [
|
||||
...Object.values(config.apis).map((api) => api?.styleguide?.pluginPaths),
|
||||
config.rawConfig?.styleguide?.pluginPaths,
|
||||
]
|
||||
.flat()
|
||||
.filter(Boolean) as string[];
|
||||
.filter(isTruthy);
|
||||
|
||||
return apiName
|
||||
? new Config(
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isRedoclyRegistryURL } from '../../redocly';
|
||||
import { Oas3Decorator, Oas2Decorator } from '../../visitors';
|
||||
|
||||
export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
|
||||
let registryDependencies = new Set<string>();
|
||||
const registryDependencies = new Set<string>();
|
||||
|
||||
return {
|
||||
DefinitionRoot: {
|
||||
|
||||
@@ -36,8 +36,8 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
|
||||
if (skipLines > 0 && i >= endLineNum - skipLines) break;
|
||||
const line = lines[i - 1] || '';
|
||||
if (line !== '') currentPad = padSize(line);
|
||||
let startIdx = i === startLineNum ? start.col - 1 : currentPad;
|
||||
let endIdx = i === endLineNum ? end.col - 1 : line.length;
|
||||
const startIdx = i === startLineNum ? start.col - 1 : currentPad;
|
||||
const endIdx = i === endLineNum ? end.col - 1 : line.length;
|
||||
|
||||
prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, red)]);
|
||||
if (!color) prefixedLines.push(['', underlineLine(line, startIdx, endIdx)]);
|
||||
|
||||
@@ -185,7 +185,7 @@ export function formatProblems(
|
||||
totals,
|
||||
version,
|
||||
problems: problems.map((p) => {
|
||||
let problem = {
|
||||
const problem = {
|
||||
...p,
|
||||
location: p.location.map((location: any) => ({
|
||||
...location,
|
||||
@@ -320,6 +320,7 @@ const groupByFiles = (problems: NormalizedProblem[]) => {
|
||||
};
|
||||
|
||||
function xmlEscape(s: string): string {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return s.replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, (char) => {
|
||||
switch (char) {
|
||||
case '<':
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist } from './utils';
|
||||
export { BundleOutputFormat, readFileFromUrl, slash, doesYamlFileExist, isTruthy } from './utils';
|
||||
export { Oas3_1Types } from './types/oas3_1';
|
||||
export { Oas3Types } from './types/oas3';
|
||||
export { Oas2Types } from './types/oas2';
|
||||
export { ConfigTypes } from './types/redocly-yaml';
|
||||
export {
|
||||
export type {
|
||||
Oas3Definition,
|
||||
Oas3_1Definition,
|
||||
Oas3Components,
|
||||
@@ -15,9 +15,10 @@ export {
|
||||
Oas3Tag,
|
||||
Oas3_1Webhooks,
|
||||
Referenced,
|
||||
OasRef,
|
||||
} from './typings/openapi';
|
||||
export { Oas2Definition } from './typings/swagger';
|
||||
export { StatsAccumulator, StatsName } from './typings/common';
|
||||
export type { Oas2Definition } from './typings/swagger';
|
||||
export type { StatsAccumulator, StatsName } from './typings/common';
|
||||
export { normalizeTypes } from './types';
|
||||
export { Stats } from './rules/other/stats';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// TODO: add a type for "types" https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/js-yaml/index.d.ts
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { JSON_SCHEMA, types, LoadOptions, DumpOptions, load, dump } from 'js-yaml';
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ export class RedoclyClient {
|
||||
|
||||
const credentials = {
|
||||
...this.readCredentialsFile(credentialsPath),
|
||||
[this.region!]: accessToken,
|
||||
[this.region]: accessToken,
|
||||
token: accessToken, // FIXME: backward compatibility, remove on 1.0.0
|
||||
};
|
||||
this.accessTokens = credentials;
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
export namespace RegistryApiTypes {
|
||||
interface VersionParams {
|
||||
interface VersionParams {
|
||||
organizationId: string;
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PrepareFileuploadParams extends VersionParams {
|
||||
export interface PrepareFileuploadParams extends VersionParams {
|
||||
filesHash: string;
|
||||
filename: string;
|
||||
isUpsert?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PushApiParams extends VersionParams {
|
||||
export interface PushApiParams extends VersionParams {
|
||||
rootFilePath: string;
|
||||
filePaths: string[];
|
||||
branch?: string;
|
||||
@@ -19,16 +18,15 @@ export namespace RegistryApiTypes {
|
||||
isPublic?: boolean;
|
||||
batchId?: string;
|
||||
batchSize?: number;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PrepareFileuploadOKResponse {
|
||||
export interface PrepareFileuploadOKResponse {
|
||||
filePath: string;
|
||||
signedUploadUrl: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface NotFoundProblemResponse {
|
||||
export interface NotFoundProblemResponse {
|
||||
status: 404;
|
||||
title: 'Not Found';
|
||||
code: 'ORGANIZATION_NOT_FOUND' | 'API_VERSION_NOT_FOUND';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import fetch, { RequestInit, HeadersInit } from 'node-fetch';
|
||||
import { RegistryApiTypes } from './registry-api-types';
|
||||
import type {
|
||||
NotFoundProblemResponse,
|
||||
PrepareFileuploadOKResponse,
|
||||
PrepareFileuploadParams,
|
||||
PushApiParams,
|
||||
} from './registry-api-types';
|
||||
import type { AccessTokens, Region } from '../config/types';
|
||||
import { DEFAULT_REGION, DOMAINS } from '../config/config';
|
||||
import { isNotEmptyObject } from '../utils';
|
||||
const version = require('../../package.json').version;
|
||||
|
||||
import type { AccessTokens, Region } from '../config/types';
|
||||
|
||||
export class RegistryApi {
|
||||
constructor(private accessTokens: AccessTokens, private region: Region) {}
|
||||
|
||||
@@ -39,7 +43,7 @@ export class RegistryApi {
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
const body: RegistryApiTypes.NotFoundProblemResponse = await response.json();
|
||||
const body: NotFoundProblemResponse = await response.json();
|
||||
throw new Error(body.code);
|
||||
}
|
||||
|
||||
@@ -71,7 +75,7 @@ export class RegistryApi {
|
||||
filesHash,
|
||||
filename,
|
||||
isUpsert,
|
||||
}: RegistryApiTypes.PrepareFileuploadParams): Promise<RegistryApiTypes.PrepareFileuploadOKResponse> {
|
||||
}: PrepareFileuploadParams): Promise<PrepareFileuploadOKResponse> {
|
||||
const response = await this.request(
|
||||
`/${organizationId}/${name}/${version}/prepare-file-upload`,
|
||||
{
|
||||
@@ -107,7 +111,7 @@ export class RegistryApi {
|
||||
isPublic,
|
||||
batchId,
|
||||
batchSize,
|
||||
}: RegistryApiTypes.PushApiParams) {
|
||||
}: PushApiParams) {
|
||||
const response = await this.request(
|
||||
`/${organizationId}/${name}/${version}`,
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Source } from './resolve';
|
||||
import { OasRef } from './typings/openapi';
|
||||
import { isTruthy } from './utils';
|
||||
|
||||
export function joinPointer(base: string, key: string | number) {
|
||||
if (base === '') base = '#/';
|
||||
@@ -45,7 +46,7 @@ export function parseRef(ref: string): { uri: string | null; pointer: string[] }
|
||||
const [uri, pointer] = ref.split('#/');
|
||||
return {
|
||||
uri: uri || null,
|
||||
pointer: pointer ? pointer.split('/').map(unescapePointer).filter(Boolean) : [],
|
||||
pointer: pointer ? pointer.split('/').map(unescapePointer).filter(isTruthy) : [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ export async function resolveDocument(opts: {
|
||||
: document;
|
||||
} catch (error) {
|
||||
const resolvedRef = {
|
||||
resolved: false as false,
|
||||
resolved: false as const,
|
||||
isRemote,
|
||||
document: undefined,
|
||||
error: error,
|
||||
@@ -334,7 +334,7 @@ export async function resolveDocument(opts: {
|
||||
}
|
||||
|
||||
let resolvedRef: ResolvedRef = {
|
||||
resolved: true as true,
|
||||
resolved: true as const,
|
||||
document: targetDoc,
|
||||
isRemote,
|
||||
node: document.parsed,
|
||||
@@ -344,7 +344,7 @@ export async function resolveDocument(opts: {
|
||||
let target = targetDoc.parsed as any;
|
||||
|
||||
const segments = pointer;
|
||||
for (let segment of segments) {
|
||||
for (const segment of segments) {
|
||||
if (typeof target !== 'object') {
|
||||
target = undefined;
|
||||
break;
|
||||
|
||||
@@ -73,7 +73,7 @@ export function validateJsonSchema(
|
||||
|
||||
function beatifyErrorMessage(error: ErrorObject) {
|
||||
let message = error.message;
|
||||
let suggest = error.keyword === 'enum' ? error.params.allowedValues : undefined;
|
||||
const suggest = error.keyword === 'enum' ? error.params.allowedValues : undefined;
|
||||
if (suggest) {
|
||||
message += ` ${suggest.map((e: any) => `"${e}"`).join(', ')}`;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const asserts: Asserts = {
|
||||
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||
const values = runOnValue(value) ? [value] : value;
|
||||
const regx = regexFromString(condition);
|
||||
for (let _val of values) {
|
||||
for (const _val of values) {
|
||||
if (!regx?.test(_val)) {
|
||||
return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export const asserts: Asserts = {
|
||||
enum: (value: string | string[], condition: string[], baseLocation: Location) => {
|
||||
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||
const values = runOnValue(value) ? [value] : value;
|
||||
for (let _val of values) {
|
||||
for (const _val of values) {
|
||||
if (!condition.includes(_val)) {
|
||||
return {
|
||||
isValid: false,
|
||||
@@ -81,7 +81,7 @@ export const asserts: Asserts = {
|
||||
disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
|
||||
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||
const values = runOnValue(value) ? [value] : value;
|
||||
for (let _val of values) {
|
||||
for (const _val of values) {
|
||||
if (condition.includes(_val)) {
|
||||
return {
|
||||
isValid: false,
|
||||
@@ -114,7 +114,7 @@ export const asserts: Asserts = {
|
||||
casing: (value: string | string[], condition: string, baseLocation: Location) => {
|
||||
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||
const values: string[] = runOnValue(value) ? [value] : value;
|
||||
for (let _val of values) {
|
||||
for (const _val of values) {
|
||||
let matchCase = false;
|
||||
switch (condition) {
|
||||
case 'camelCase':
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AssertToApply, buildSubjectVisitor, buildVisitorObject } from './utils'
|
||||
import { Oas2Rule, Oas3Rule } from '../../../visitors';
|
||||
|
||||
export const Assertions: Oas3Rule | Oas2Rule = (opts: object) => {
|
||||
let visitors: any[] = [];
|
||||
const visitors: any[] = [];
|
||||
|
||||
// As 'Assertions' has an array of asserts,
|
||||
// that array spreads into an 'opts' object on init rules phase here
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Oas2SecurityScheme } from '../../typings/swagger';
|
||||
import { Oas3SecurityScheme } from '../../typings/openapi';
|
||||
|
||||
export const OperationSecurityDefined: Oas3Rule | Oas2Rule = () => {
|
||||
let referencedSchemes = new Map<
|
||||
const referencedSchemes = new Map<
|
||||
string,
|
||||
{
|
||||
defined?: boolean;
|
||||
|
||||
@@ -28,7 +28,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
||||
const required =
|
||||
typeof type.required === 'function' ? type.required(node, key) : type.required;
|
||||
|
||||
for (let propName of required || []) {
|
||||
for (const propName of required || []) {
|
||||
if (!(node as object).hasOwnProperty(propName)) {
|
||||
report({
|
||||
message: `The field \`${propName}\` must be present on this level.`,
|
||||
@@ -57,7 +57,7 @@ export const OasSpec: Oas3Rule | Oas2Rule = () => {
|
||||
const requiredOneOf = type.requiredOneOf || null;
|
||||
if (requiredOneOf) {
|
||||
let hasProperty = false;
|
||||
for (let propName of requiredOneOf || []) {
|
||||
for (const propName of requiredOneOf || []) {
|
||||
if ((node as object).hasOwnProperty(propName)) {
|
||||
hasProperty = true;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Oas2Components } from '../../typings/swagger';
|
||||
import { isEmptyObject } from '../../utils';
|
||||
|
||||
export const RemoveUnusedComponents: Oas2Rule = () => {
|
||||
let components = new Map<
|
||||
const components = new Map<
|
||||
string,
|
||||
{ used: boolean; componentType?: keyof Oas2Components; name: string }
|
||||
>();
|
||||
@@ -39,7 +39,7 @@ export const RemoveUnusedComponents: Oas2Rule = () => {
|
||||
const data = ctx.getVisitorData() as { removedCount: number };
|
||||
data.removedCount = 0;
|
||||
|
||||
let rootComponents = new Set<keyof Oas2Components>();
|
||||
const rootComponents = new Set<keyof Oas2Components>();
|
||||
components.forEach((usageInfo) => {
|
||||
const { used, name, componentType } = usageInfo;
|
||||
if (!used && componentType) {
|
||||
|
||||
@@ -48,7 +48,7 @@ function checkEnumVariables(server: Oas3Server): enumError[] | undefined {
|
||||
if (server.variables && Object.keys(server.variables).length === 0) return;
|
||||
|
||||
const errors: enumError[] = [];
|
||||
for (var variable in server.variables) {
|
||||
for (const variable in server.variables) {
|
||||
const serverVariable = server.variables[variable];
|
||||
if (!serverVariable.enum) continue;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Oas3Rule } from '../../visitors';
|
||||
import { Location } from '../../ref-utils';
|
||||
|
||||
export const NoUnusedComponents: Oas3Rule = () => {
|
||||
let components = new Map<string, { used: boolean; location: Location; name: string }>();
|
||||
const components = new Map<string, { used: boolean; location: Location; name: string }>();
|
||||
|
||||
function registerComponent(location: Location, name: string): void {
|
||||
components.set(location.absolutePointer, {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Oas3Components } from '../../typings/openapi';
|
||||
import { isEmptyObject } from '../../utils';
|
||||
|
||||
export const RemoveUnusedComponents: Oas3Rule = () => {
|
||||
let components = new Map<
|
||||
const components = new Map<
|
||||
string,
|
||||
{ used: boolean; componentType?: keyof Oas3Components; name: string }
|
||||
>();
|
||||
@@ -45,12 +45,12 @@ export const RemoveUnusedComponents: Oas3Rule = () => {
|
||||
|
||||
components.forEach((usageInfo) => {
|
||||
const { used, componentType, name } = usageInfo;
|
||||
if (!used && componentType) {
|
||||
let componentChild = root.components![componentType];
|
||||
if (!used && componentType && root.components) {
|
||||
const componentChild = root.components[componentType];
|
||||
delete componentChild![name];
|
||||
data.removedCount++;
|
||||
if (isEmptyObject(componentChild)) {
|
||||
delete root.components![componentType];
|
||||
delete root.components[componentType];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ export function validateExample(
|
||||
allowAdditionalProperties
|
||||
);
|
||||
if (!valid) {
|
||||
for (let error of errors) {
|
||||
for (const error of errors) {
|
||||
report({
|
||||
message: `Example value must conform to the schema: ${error.message}.`,
|
||||
location: {
|
||||
|
||||
@@ -96,7 +96,7 @@ export function omitObjectProps<T extends Record<string, unknown>>(
|
||||
export function splitCamelCaseIntoWords(str: string) {
|
||||
const camel = str
|
||||
.split(/(?:[-._])|([A-Z][a-z]+)/)
|
||||
.filter(Boolean)
|
||||
.filter(isTruthy)
|
||||
.map((item) => item.toLocaleLowerCase());
|
||||
const caps = str
|
||||
.split(/([A-Z]{2,})/)
|
||||
@@ -183,7 +183,7 @@ export function isNotString<T>(value: string | T): value is T {
|
||||
}
|
||||
|
||||
export function assignExisting<T>(target: Record<string, T>, obj: Record<string, T>) {
|
||||
for (let k of Object.keys(obj)) {
|
||||
for (const k of Object.keys(obj)) {
|
||||
if (target.hasOwnProperty(k)) {
|
||||
target[k] = obj[k];
|
||||
}
|
||||
@@ -217,3 +217,9 @@ export function showWarningForDeprecatedField(deprecatedField: string, updatedFi
|
||||
export function showErrorForDeprecatedField(deprecatedField: string, updatedField: string) {
|
||||
throw new Error(`Do not use '${deprecatedField}' field. Use '${updatedField}' instead.\n`);
|
||||
}
|
||||
|
||||
export type Falsy = undefined | null | false | '' | 0;
|
||||
|
||||
export function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy {
|
||||
return !!value;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ type NestedVisitObject<T, P> = VisitObject<T> & NestedVisitor<P>;
|
||||
|
||||
type VisitFunctionOrObject<T> = VisitFunction<T> | VisitObject<T>;
|
||||
|
||||
type VisitorNode<T extends any> = {
|
||||
type VisitorNode<T> = {
|
||||
ruleId: string;
|
||||
severity: ProblemSeverity;
|
||||
context: VisitorLevelContext | VisitorSkippedLevelContext;
|
||||
@@ -309,7 +309,7 @@ export function normalizeVisitors<T extends BaseVisitor>(
|
||||
|
||||
const possibleChildren = new Set<NormalizedNodeType>();
|
||||
|
||||
for (let type of Object.values(from.properties)) {
|
||||
for (const type of Object.values(from.properties)) {
|
||||
if (type === to) {
|
||||
addWeakFromStack(ruleConf, stack);
|
||||
continue;
|
||||
@@ -333,7 +333,7 @@ export function normalizeVisitors<T extends BaseVisitor>(
|
||||
}
|
||||
}
|
||||
|
||||
for (let fromType of Array.from(possibleChildren.values())) {
|
||||
for (const fromType of Array.from(possibleChildren.values())) {
|
||||
addWeakNodes(ruleConf, fromType, to, parentContext, stack);
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ export function normalizeVisitors<T extends BaseVisitor>(
|
||||
visit: () => undefined,
|
||||
depth: 0,
|
||||
context: {
|
||||
isSkippedLevel: true as true,
|
||||
isSkippedLevel: true,
|
||||
seen: new Set(),
|
||||
parent: parentContext,
|
||||
},
|
||||
@@ -407,7 +407,7 @@ export function normalizeVisitors<T extends BaseVisitor>(
|
||||
activatedOn: null,
|
||||
type: types[typeName],
|
||||
parent: parentContext,
|
||||
isSkippedLevel: false as false,
|
||||
isSkippedLevel: false,
|
||||
};
|
||||
|
||||
if (typeof typeVisitor === 'object') {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
|
||||
Reference in New Issue
Block a user