Repository: Redocly/openapi-cli
Pull request #357 (2021-09-02 11:33:37)

fix: improve types for completion config
## What/Why/How?
1. improve config types for completion
2. improve locations in missing and empty keys


## Reference

## Testing

## Screenshots (optional)

## Check yourself

- [x] Code is linted
- [x] Tested with redoc/reference-docs/workflows
- [x] All new/updated code is covered with tests

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines



Patch:
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index ef9e0d74..e5ee032c 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -2,6 +2,7 @@ export { BundleOutputFormat, readFileFromUrl } 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 {
   Oas3Definition,
   Oas3Components,
diff --git a/packages/core/src/lint.ts b/packages/core/src/lint.ts
index c1d885aa..832a7359 100755
--- a/packages/core/src/lint.ts
+++ b/packages/core/src/lint.ts
@@ -12,7 +12,7 @@ import { normalizeTypes } from './types';
 import { initRules } from './config/rules';
 import { releaseAjvInstance } from './rules/ajv';
 import { detectOpenAPI, OasMajorVersion, OasVersion, openAPIMajor } from './oas-types';
-import { configTypes } from './types/redocly-yaml';
+import { ConfigTypes } from './types/redocly-yaml';
 import { OasSpec } from './rules/common/spec';
 import { defaultPlugin } from './config/builtIn';
 
@@ -108,7 +108,7 @@ export async function lintConfig(opts: {
     rules: { spec: 'error' },
   });
 
-  const types = normalizeTypes(configTypes, config);
+  const types = normalizeTypes(ConfigTypes, config);
   const rules = [{ severity: 'error' as ProblemSeverity, ruleId: 'spec', visitor: OasSpec({ severity: 'error' }) }];
   const normalizedVisitors = normalizeVisitors(rules, types);
 
diff --git a/packages/core/src/rules/utils.ts b/packages/core/src/rules/utils.ts
index f63c45ff..1b1f1347 100644
--- a/packages/core/src/rules/utils.ts
+++ b/packages/core/src/rules/utils.ts
@@ -53,6 +53,7 @@ export function validateDefinedAndNonEmpty(fieldName: string, value: any, ctx: U
   if (value[fieldName] === undefined) {
     ctx.report({
       message: missingRequiredField(ctx.type.name, fieldName),
+      location: ctx.location.child([fieldName]).key(),
     });
   } else if (!value[fieldName]) {
     ctx.report({
diff --git a/packages/core/src/types/redocly-yaml.ts b/packages/core/src/types/redocly-yaml.ts
index 1f13c34a..400b3184 100644
--- a/packages/core/src/types/redocly-yaml.ts
+++ b/packages/core/src/types/redocly-yaml.ts
@@ -1,8 +1,12 @@
-import { NodeType } from '.';
+import { NodeType, listOf } from '.';
 
 const ConfigRoot: NodeType = {
   properties: {
-    apiDefinitions: { type: 'object', additionalProperties: { type: 'string' } },
+    apiDefinitions: {
+      type: 'object',
+      properties: {},
+      additionalProperties: { properties: { type: 'string' } }
+    },
     lint: 'ConfigLint',
     referenceDocs: 'ConfigReferenceDocs',
   },
@@ -15,8 +19,8 @@ const ConfigHTTP: NodeType = {
       items: {
         type: 'string',
       },
-    }
-  }
+    },
+  },
 };
 
 const ConfigLint: NodeType = {
@@ -32,32 +36,84 @@ const ConfigLint: NodeType = {
       },
     },
     doNotResolveExamples: { type: 'boolean' },
-    rules: { type: 'object'},
-    oas2Rules: { type: 'object'},
-    oas3_0Rules: { type: 'object'},
-    oas3_1Rules: { type: 'object'},
-    preprocessors: { type: 'object'},
-    oas2Preprocessors: { type: 'object'},
-    oas3_0Preprocessors: { type: 'object'},
-    oas3_1Preprocessors: { type: 'object'},
-    decorators: { type: 'object'},
-    oas2Decorators: { type: 'object'},
-    oas3_0Decorators: { type: 'object'},
-    oas3_1Decorators: { type: 'object'},
+    rules: { type: 'object' },
+    oas2Rules: { type: 'object' },
+    oas3_0Rules: { type: 'object' },
+    oas3_1Rules: { type: 'object' },
+    preprocessors: { type: 'object' },
+    oas2Preprocessors: { type: 'object' },
+    oas3_0Preprocessors: { type: 'object' },
+    oas3_1Preprocessors: { type: 'object' },
+    decorators: { type: 'object' },
+    oas2Decorators: { type: 'object' },
+    oas3_0Decorators: { type: 'object' },
+    oas3_1Decorators: { type: 'object' },
     resolve: {
       properties: {
         http: 'ConfigHTTP',
-      }
-    }
-  }
+      },
+    },
+  },
 };
 
+const ConfigLanguage: NodeType = {
+  properties: {
+    label: { type: 'string' },
+    lang: { type: 'string' },
+  },
+};
+
+const ConfigLabels: NodeType = {
+  properties: {
+    enum: { type: 'string' },
+    enumSingleValue: { type: 'string' },
+    enumArray: { type: 'string' },
+    default: { type: 'string' },
+    deprecated: { type: 'string' },
+    example: { type: 'string' },
+    examples: { type: 'string' },
+    nullable: { type: 'string' },
+    recursive: { type: 'string' },
+    arrayOf: { type: 'string' },
+    webhook: { type: 'string' },
+    authorizations: { type: 'string' },
+    tryItAuthBasicUsername: { type: 'string' },
+    tryItAuthBasicPassword: { type: 'string' },
+  },
+};
+
+const ConfigSidebarLinks: NodeType = {
+  properties: {
+    placement: { type: 'string' },
+    label: { type: 'string' },
+    link: { type: 'string' },
+    target: { type: 'string' },
+  },
+};
+
+const ConfigTheme: NodeType = {
+  properties: {
+    breakpoints: { type: 'object', additionalProperties: { type: 'string' } },
+    codeBlock: { type: 'object', additionalProperties: { type: 'string' } },
+    colors: { type: 'object', additionalProperties: { type: 'string' } },
+    components: { type: 'object', additionalProperties: { type: 'string' } },
+    layout: { type: 'object', additionalProperties: { type: 'string' } },
+    logo: { type: 'object', additionalProperties: { type: 'string' } },
+    overrides: { type: 'object', additionalProperties: { type: 'string' } },
+    rightPanel: { type: 'object', additionalProperties: { type: 'string' } },
+    schema: { type: 'object', additionalProperties: { type: 'string' } },
+    shape: { type: 'object', additionalProperties: { type: 'string' } },
+    sidebar: { type: 'object', additionalProperties: { type: 'string' } },
+    spacing: { type: 'object', additionalProperties: { type: 'string' } },
+    typography: { type: 'object', additionalProperties: { type: 'string' } },
+    links: { properties: { color: { type: 'string' } } },
+    codeSample: { properties: { backgroundColor: { type: 'string' } } },
+  },
+}
+
 const ConfigReferenceDocs: NodeType = {
   properties: {
-    theme: {
-      type: 'object',
-      additionalProperties: { type: 'string' },
-    },
+    theme: 'ConfigTheme',
     corsProxyUrl: { type: 'string' },
     ctrlFHijack: { type: 'boolean' },
     defaultSampleLanguage: { type: 'string' },
@@ -68,7 +124,12 @@ const ConfigReferenceDocs: NodeType = {
     expandDefaultServerVariables: { type: 'boolean' },
     expandResponses: { type: 'string' },
     expandSingleSchemaField: { type: 'boolean' },
-    generateCodeSamples: { type: 'object' },
+    generateCodeSamples: {
+      properties: {
+        skipOptionalParameters: { type: 'boolean' },
+        languages: listOf('ConfigLanguage'),
+      },
+    },
     generatedPayloadSamplesMaxDepth: { type: 'number' },
     hideDownloadButton: { type: 'boolean' },
     hideHostname: { type: 'boolean' },
@@ -80,8 +141,8 @@ const ConfigReferenceDocs: NodeType = {
     hideSchemaTitles: { type: 'boolean' },
     hideSingleRequestSampleTab: { type: 'boolean' },
     htmlTemplate: { type: 'string' },
-    jsonSampleExpandLevel: { type: 'object' },
-    labels: { type: 'object' },
+    jsonSampleExpandLevel: { type: 'string' },
+    labels: 'ConfigLabels',
     layout: { type: 'object' },
     maxDisplayedEnumValues: { type: 'number' },
     menuToggle: { type: 'boolean' },
@@ -109,7 +170,7 @@ const ConfigReferenceDocs: NodeType = {
     showExtensions: { type: 'boolean' },
     showNextButton: { type: 'boolean' },
     showRightPanelToggle: { type: 'boolean' },
-    sidebarLinks: { type: 'object', additionalProperties: { type: 'string' } },
+    sidebarLinks: 'ConfigSidebarLinks',
     sideNavStyle: { type: 'string' },
     simpleOneOfTypeLabel: { type: 'boolean' },
     sortEnumValuesAlphabetically: { type: 'boolean' },
@@ -122,9 +183,13 @@ const ConfigReferenceDocs: NodeType = {
   additionalProperties: { type: 'string' },
 };
 
-export const configTypes: Record<string, NodeType> = {
+export const ConfigTypes: Record<string, NodeType> = {
   ConfigRoot,
   ConfigLint,
   ConfigReferenceDocs,
   ConfigHTTP,
+  ConfigLanguage,
+  ConfigLabels,
+  ConfigSidebarLinks,
+  ConfigTheme,
 };


Test patch:
diff --git a/__tests__/info-description-not-present-warning/snapshot.js b/__tests__/info-description-not-present-warning/snapshot.js
index 07adaa67..b2158130 100644
--- a/__tests__/info-description-not-present-warning/snapshot.js
+++ b/__tests__/info-description-not-present-warning/snapshot.js
@@ -3,18 +3,15 @@
 exports[`E2E info-description-not-present-warning 1`] = `
 
 validating /openapi.yaml...
-[1] openapi.yaml:3:3 at #/info
+[1] openapi.yaml:2:1 at #/info/description
 
 Info object should contain \`description\` field.
 
 1 | openapi: 3.0.0
 2 | info:
+  | ^^^^
 3 |   title: Example OpenAPI 3 definition.
-  |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 4 |   version: 1.0.0
-  |   ^^^^^^^^^^^^^^
-5 |
-6 | paths:
 
 Warning was generated by the info-description rule.
 
diff --git a/__tests__/info-license-url-missing-warning/snapshot.js b/__tests__/info-license-url-missing-warning/snapshot.js
index e46277e5..6b2138a2 100644
--- a/__tests__/info-license-url-missing-warning/snapshot.js
+++ b/__tests__/info-license-url-missing-warning/snapshot.js
@@ -3,16 +3,16 @@
 exports[`E2E info-license-url-missing-warning 1`] = `
 
 validating /openapi.yaml...
-[1] openapi.yaml:6:5 at #/info/license
+[1] openapi.yaml:5:3 at #/info/license/url
 
 License object should contain \`url\` field.
 
-4 |   version: 1.0
-5 |   license:
-6 |     name: Apache 2.0
-  |     ^^^^^^^^^^^^^^^^
+3 | title: Example OpenAPI 3 definition.
+4 | version: 1.0
+5 | license:
+  | ^^^^^^^
+6 |   name: Apache 2.0
 7 |
-8 | paths:
 
 Warning was generated by the info-license-url rule.
 
diff --git a/__tests__/operation-description-warning/snapshot.js b/__tests__/operation-description-warning/snapshot.js
index 94eba61d..32d9a7f1 100644
--- a/__tests__/operation-description-warning/snapshot.js
+++ b/__tests__/operation-description-warning/snapshot.js
@@ -3,20 +3,16 @@
 exports[`E2E operation-description-warning 1`] = `
 
 validating /openapi.yaml...
-[1] openapi.yaml:9:7 at #/paths/~1ping/get
+[1] openapi.yaml:8:5 at #/paths/~1ping/get/description
 
 Operation object should contain \`description\` field.
 
- 7 | '/ping':
- 8 |   get:
- 9 |     operationId: gitPing
-   |     ^^^^^^^^^^^^^^^^^^^^
-10 |     summary: example text
-   |     ^^^^^^^^^^^^^^^^^^^^^
- … |     < 2 more lines >
-13 |         description: example description
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-14 |
+ 6 | paths:
+ 7 |   '/ping':
+ 8 |     get:
+   |     ^^^
+ 9 |       operationId: gitPing
+10 |       summary: example text
 
 Warning was generated by the operation-description rule.
 
diff --git a/__tests__/turn-on-all-rules/snapshot.js b/__tests__/turn-on-all-rules/snapshot.js
index 9ef2be1e..e27fff80 100644
--- a/__tests__/turn-on-all-rules/snapshot.js
+++ b/__tests__/turn-on-all-rules/snapshot.js
@@ -19,18 +19,15 @@ Servers must be present.
 Error was generated by the no-empty-servers rule.
 
 
-[2] openapi.yaml:3:3 at #/info
+[2] openapi.yaml:2:1 at #/info/description
 
 Info object should contain \`description\` field.
 
 1 | openapi: 3.0.0
 2 | info:
+  | ^^^^
 3 |   title: Example OpenAPI 3 definition.
-  |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 4 |   version: 1.0.0
-  |   ^^^^^^^^^^^^^^
-5 |
-6 | paths:
 
 Error was generated by the info-description rule.
 
@@ -64,56 +61,44 @@ Info object should contain \`license\` field.
 Error was generated by the info-license rule.
 
 
-[5] openapi.yaml:9:7 at #/paths/~1ping~1{id}~1{test}/get
+[5] openapi.yaml:8:5 at #/paths/~1ping~1{id}~1{test}/get/operationId
 
 Operation object should contain \`operationId\` field.
 
- 7 | '/ping/{id}/{test}':
- 8 |   get:
- 9 |     parameters:
-   |     ^^^^^^^^^^^
-10 |       - in: path
-   |       ^^^^^^^^^^
- … |       < 7 more lines >
-18 |         description: example description
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-19 |
+ 6 | paths:
+ 7 |   '/ping/{id}/{test}':
+ 8 |     get:
+   |     ^^^
+ 9 |       parameters:
+10 |         - in: path
 
 Error was generated by the operation-operationId rule.
 
 
-[6] openapi.yaml:9:7 at #/paths/~1ping~1{id}~1{test}/get
+[6] openapi.yaml:8:5 at #/paths/~1ping~1{id}~1{test}/get/summary
 
 Operation object should contain \`summary\` field.
 
- 7 | '/ping/{id}/{test}':
- 8 |   get:
- 9 |     parameters:
-   |     ^^^^^^^^^^^
-10 |       - in: path
-   |       ^^^^^^^^^^
- … |       < 7 more lines >
-18 |         description: example description
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-19 |
+ 6 | paths:
+ 7 |   '/ping/{id}/{test}':
+ 8 |     get:
+   |     ^^^
+ 9 |       parameters:
+10 |         - in: path
 
 Error was generated by the operation-summary rule.
 
 
-[7] openapi.yaml:9:7 at #/paths/~1ping~1{id}~1{test}/get
+[7] openapi.yaml:8:5 at #/paths/~1ping~1{id}~1{test}/get/description
 
 Operation object should contain \`description\` field.
 
- 7 | '/ping/{id}/{test}':
- 8 |   get:
- 9 |     parameters:
-   |     ^^^^^^^^^^^
-10 |       - in: path
-   |       ^^^^^^^^^^
- … |       < 7 more lines >
-18 |         description: example description
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-19 |
+ 6 | paths:
+ 7 |   '/ping/{id}/{test}':
+ 8 |     get:
+   |     ^^^
+ 9 |       parameters:
+10 |         - in: path
 
 Error was generated by the operation-description rule.
 
diff --git a/packages/core/src/__tests__/lint.test.ts b/packages/core/src/__tests__/lint.test.ts
index 14c9fec7..0b4f29c3 100644
--- a/packages/core/src/__tests__/lint.test.ts
+++ b/packages/core/src/__tests__/lint.test.ts
@@ -42,7 +42,7 @@ describe('lint', () => {
     `);
   });
 
-  it('lintDocumentConfig should work', async () => {
+  it('lintConfig should work', async () => {
     const document = parseYamlToDocument(
       outdent`
       apiDefinitions: error string
diff --git a/packages/core/src/rules/common/__tests__/info-description.test.ts b/packages/core/src/rules/common/__tests__/info-description.test.ts
index 51379418..7f1f4f9d 100644
--- a/packages/core/src/rules/common/__tests__/info-description.test.ts
+++ b/packages/core/src/rules/common/__tests__/info-description.test.ts
@@ -29,8 +29,8 @@ describe('Oas3 info-description', () => {
         Object {
           "location": Array [
             Object {
-              "pointer": "#/info",
-              "reportOnKey": false,
+              "pointer": "#/info/description",
+              "reportOnKey": true,
               "source": "foobar.yaml",
             },
           ],
diff --git a/packages/core/src/rules/common/__tests__/license-url.test.ts b/packages/core/src/rules/common/__tests__/license-url.test.ts
index 830e46c1..1d46bf8d 100644
--- a/packages/core/src/rules/common/__tests__/license-url.test.ts
+++ b/packages/core/src/rules/common/__tests__/license-url.test.ts
@@ -27,8 +27,8 @@ describe('Oas3 license-url', () => {
         Object {
           "location": Array [
             Object {
-              "pointer": "#/info/license",
-              "reportOnKey": false,
+              "pointer": "#/info/license/url",
+              "reportOnKey": true,
               "source": "foobar.yaml",
             },
           ],
diff --git a/packages/core/src/rules/common/__tests__/tag-description.test.ts b/packages/core/src/rules/common/__tests__/tag-description.test.ts
index e04c5336..bd3be8a3 100644
--- a/packages/core/src/rules/common/__tests__/tag-description.test.ts
+++ b/packages/core/src/rules/common/__tests__/tag-description.test.ts
@@ -28,8 +28,8 @@ describe('Oas3 tag-description', () => {
         Object {
           "location": Array [
             Object {
-              "pointer": "#/tags/0",
-              "reportOnKey": false,
+              "pointer": "#/tags/0/description",
+              "reportOnKey": true,
               "source": "foobar.yaml",
             },
           ],