Repository: stoplightio/spectral (ts)

Issue:
Parameters in links objects shouldn't be linted for not having a description property.
### **I'm submitting a...**
  - [ x ] bug report

### What is the current behavior?

Lint an OAS 3 with links section, where a link object has a parameters collection containing the usual name to path dictionary.
Get warnings like this:

Rule:
Parameter objects should have a description.

Details:
paths./allSchools/createUploadSession.post.responses.201.links.getUploadSessionById.parameters.sessionId.description is not truthy

But the parameters collection in a links object is not a parameters object.

### What is the expected behavior?
No warning.

### What is the motivation / use case for changing the behavior?
Correctness.

### Please tell us about your environment:
Web version.



Pull request:
Do not validate description in parameters for links
Fixes #272

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No


Patch:
diff --git a/docs/reference/openapi-rules.md b/docs/reference/openapi-rules.md
index ba03c78e..4442a4cc 100644
--- a/docs/reference/openapi-rules.md
+++ b/docs/reference/openapi-rules.md
@@ -334,12 +334,6 @@ Operation should have non-empty `tags` array.
 
 **Recommended:** Yes
 
-### parameter-description
-
-Parameter objects should have a `description`.
-
-**Recommended:** No
-
 ### path-declarations-must-exist
 
 Path parameter declarations cannot be empty, ex.`/given/{}` is invalid.
@@ -451,6 +445,12 @@ Validate structure of OpenAPI v2 specification.
 
 **Recommended:** Yes
 
+### oas2-parameter-description
+
+Parameter objects should have a `description`.
+
+**Recommended:** No
+
 ## oas3
 
 These OpenAPI v3.0-only rules can be loaded with `extends: "spectral: oas3"` and include all the oas rules.
@@ -543,3 +543,9 @@ Examples must be valid against their defined schema.
 Validate structure of OpenAPI v3 specification.
 
 **Recommended:** Yes
+
+### oas3-parameter-description
+
+Parameter objects should have a `description`.
+
+**Recommended:** No
\ No newline at end of file
diff --git a/src/rulesets/oas/index.json b/src/rulesets/oas/index.json
index 13fff27f..4a5c40e5 100644
--- a/src/rulesets/oas/index.json
+++ b/src/rulesets/oas/index.json
@@ -338,23 +338,6 @@
         "operation"
       ]
     },
-    "parameter-description": {
-      "description": "Parameter objects should have a `description`.",
-      "recommended": false,
-      "type": "style",
-      "given": "$..parameters[*]",
-      "when": {
-        "field": "@key",
-        "pattern": "^(?!.*(\\$ref))"
-      },
-      "then": {
-        "field": "description",
-        "function": "truthy"
-      },
-      "tags": [
-        "parameters"
-      ]
-    },
     "path-declarations-must-exist": {
       "description": "Path parameter declarations cannot be empty, ex.`/given/{}` is invalid.",
       "recommended": true,
diff --git a/src/rulesets/oas2/index.json b/src/rulesets/oas2/index.json
index 7504368b..3c5d2f11 100644
--- a/src/rulesets/oas2/index.json
+++ b/src/rulesets/oas2/index.json
@@ -73,6 +73,19 @@
         "function": "truthy"
       }
     },
+    "oas2-parameter-description": {
+      "description": "Parameter objects should have a `description`.",
+      "recommended": false,
+      "type": "style",
+      "given": "$..[?(@parentProperty === 'parameters' && @.in)]",
+      "then": {
+        "field": "description",
+        "function": "truthy"
+      },
+      "tags": [
+        "parameters"
+      ]
+    },
     "oas2-operation-security-defined": {
       "description": "Operation `security` values must match a scheme defined in the `securityDefinitions` object.",
       "recommended": true,
diff --git a/src/rulesets/oas3/index.json b/src/rulesets/oas3/index.json
index f9929a87..fa1e6f85 100644
--- a/src/rulesets/oas3/index.json
+++ b/src/rulesets/oas3/index.json
@@ -52,6 +52,19 @@
         "operation"
       ]
     },
+    "oas3-parameter-description": {
+      "description": "Parameter objects should have a `description`.",
+      "recommended": false,
+      "type": "style",
+      "given": "$..[?(@parentProperty !== 'links' && @.parameters)]['parameters'].[?(@.in)]",
+      "then": {
+        "field": "description",
+        "function": "truthy"
+      },
+      "tags": [
+        "parameters"
+      ]
+    },
     "server-not-example.com": {
       "description": "Server URL should not point at `example.com`.",
       "recommended": false,


Test patch:
diff --git a/src/rulesets/oas/__tests__/parameter-description.ts b/src/rulesets/__tests__/shared/_parameter-description.ts
similarity index 73%
rename from src/rulesets/oas/__tests__/parameter-description.ts
rename to src/rulesets/__tests__/shared/_parameter-description.ts
index 37de60f4..2b0170e7 100644
--- a/src/rulesets/oas/__tests__/parameter-description.ts
+++ b/src/rulesets/__tests__/shared/_parameter-description.ts
@@ -1,16 +1,7 @@
-import { RuleType, Spectral } from '../../../spectral';
-import * as ruleset from '../index.json';
-
-describe('parameter-description', () => {
-  const s = new Spectral();
-
-  s.setRules({
-    'parameter-description': Object.assign(ruleset.rules['parameter-description'], {
-      recommended: true,
-      type: RuleType[ruleset.rules['parameter-description'].type],
-    }),
-  });
+import { DiagnosticSeverity } from '@stoplight/types';
+import { Spectral } from '../../../spectral';
 
+export default (s: Spectral, oasVersion: number) => {
   test('should work for shared level parameters', async () => {
     const results = await s.run({
       swagger: '2.0',
@@ -66,27 +57,6 @@ describe('parameter-description', () => {
     expect(results.length).toEqual(0);
   });
 
-  test('return errors if shared level parameter description is missing', async () => {
-    const results = await s.run({
-      swagger: '2.0',
-      parameters: {
-        limit: {
-          name: 'limit',
-          in: 'query',
-          type: 'integer',
-        },
-      },
-    });
-    expect(results).toEqual([
-      expect.objectContaining({
-        code: 'parameter-description',
-        message: 'Parameter objects should have a `description`.',
-        path: ['parameters', 'limit'],
-        severity: 1,
-      }),
-    ]);
-  });
-
   test('return errors if top level path parameter description is missing', async () => {
     const results = await s.run({
       swagger: '2.0',
@@ -104,10 +74,10 @@ describe('parameter-description', () => {
     });
     expect(results).toEqual([
       expect.objectContaining({
-        code: 'parameter-description',
+        code: `oas${oasVersion}-parameter-description`,
         message: 'Parameter objects should have a `description`.',
         path: ['paths', '/todos', 'parameters', '0'],
-        severity: 1,
+        severity: DiagnosticSeverity.Warning,
       }),
     ]);
   });
@@ -131,7 +101,7 @@ describe('parameter-description', () => {
     });
     expect(results).toEqual([
       expect.objectContaining({
-        code: 'parameter-description',
+        code: `oas${oasVersion}-parameter-description`,
         message: 'Parameter objects should have a `description`.',
         path: ['paths', '/todos', 'get', 'parameters', '0'],
         severity: 1,
@@ -155,4 +125,4 @@ describe('parameter-description', () => {
       }),
     ).not.rejects;
   });
-});
+};
diff --git a/src/rulesets/oas2/__tests__/oas2-parameter-description.ts b/src/rulesets/oas2/__tests__/oas2-parameter-description.ts
new file mode 100644
index 00000000..1187c2e4
--- /dev/null
+++ b/src/rulesets/oas2/__tests__/oas2-parameter-description.ts
@@ -0,0 +1,38 @@
+import { DiagnosticSeverity } from '@stoplight/types';
+import { RuleType, Spectral } from '../../../spectral';
+import testParameterDescription from '../../__tests__/shared/_parameter-description';
+import * as ruleset from '../index.json';
+
+describe('oas2-parameter-description', () => {
+  const s = new Spectral();
+
+  s.setRules({
+    'oas2-parameter-description': Object.assign(ruleset.rules['oas2-parameter-description'], {
+      recommended: true,
+      type: RuleType[ruleset.rules['oas2-parameter-description'].type],
+    }),
+  });
+
+  testParameterDescription(s, 2);
+
+  test('return errors if shared level parameter description is missing', async () => {
+    const results = await s.run({
+      swagger: '2.0',
+      parameters: {
+        limit: {
+          name: 'limit',
+          in: 'query',
+          type: 'integer',
+        },
+      },
+    });
+    expect(results).toEqual([
+      expect.objectContaining({
+        code: 'oas2-parameter-description',
+        message: 'Parameter objects should have a `description`.',
+        path: ['parameters', 'limit'],
+        severity: DiagnosticSeverity.Warning,
+      }),
+    ]);
+  });
+});
diff --git a/src/rulesets/oas3/__tests__/oas3-parameter-description.ts b/src/rulesets/oas3/__tests__/oas3-parameter-description.ts
new file mode 100644
index 00000000..908b622b
--- /dev/null
+++ b/src/rulesets/oas3/__tests__/oas3-parameter-description.ts
@@ -0,0 +1,95 @@
+import { DiagnosticSeverity } from '@stoplight/types';
+import { RuleType, Spectral } from '../../../spectral';
+import testParameterDescription from '../../__tests__/shared/_parameter-description';
+import * as ruleset from '../index.json';
+
+describe('oas3-parameter-description', () => {
+  const s = new Spectral();
+
+  s.setRules({
+    'oas3-parameter-description': Object.assign(ruleset.rules['oas3-parameter-description'], {
+      recommended: true,
+      type: RuleType[ruleset.rules['oas3-parameter-description'].type],
+    }),
+  });
+
+  testParameterDescription(s, 3);
+
+  describe('$.components.parameters', () => {
+    it('validates description', async () => {
+      const results = await s.run({
+        openapi: '3.0.2',
+        components: {
+          parameters: {
+            address: {
+              in: 'body',
+            },
+          },
+        },
+      });
+
+      expect(results).toEqual([
+        expect.objectContaining({
+          code: 'oas3-parameter-description',
+          message: 'Parameter objects should have a `description`.',
+          path: ['components', 'parameters', 'address'],
+          severity: DiagnosticSeverity.Warning,
+        }),
+      ]);
+    });
+  });
+
+  describe('description for parameters in links', () => {
+    describe('$.components.links', () => {
+      it('does not validate description', async () => {
+        const results = await s.run({
+          openapi: '3.0.2',
+          components: {
+            links: {
+              address: {
+                operationId: 'getUserAddressByUUID',
+                parameters: {
+                  param: {
+                    value: 'value',
+                    in: 'header',
+                  },
+                },
+              },
+            },
+          },
+        });
+
+        expect(results).toEqual([]);
+      });
+    });
+
+    describe('links in a response', () => {
+      it('does not validate description', async () => {
+        const results = await s.run({
+          paths: {
+            '/pets': {
+              get: {
+                responses: {
+                  '200': {
+                    links: {
+                      abc: {
+                        parameters: {
+                          param: {
+                            in: 'body',
+                            val: 2,
+                          },
+                        },
+                      },
+                    },
+                  },
+                },
+              },
+            },
+          },
+        });
+
+        expect(results).toEqual([]);
+      });
+    });
+  });
+});
diff --git a/test-harness/scenarios/parameter-description-links.oas3.scenario b/test-harness/scenarios/parameter-description-links.oas3.scenario
new file mode 100644
index 00000000..93ee4b21
--- /dev/null
+++ b/test-harness/scenarios/parameter-description-links.oas3.scenario
@@ -0,0 +1,28 @@
+====test====
+Parameters in links are not validated to have description.
+====document====
+openapi: 3.0.2
+paths:
+  /pets:
+    get:
+      responses:
+        200:
+          description: Expected response to a valid request
+          links:
+            abc:
+              parameters:
+                xyz: 123
+                abc:
+                  in: header
+                  val: abc
+components:
+  links:
+    address:
+      operationId: getUserAddressByUUID
+      parameters:
+        userUuid: abc
+====command====
+lint --ruleset ./test-harness/scenarios/rulesets/parameter-description.oas3.yaml {document}
+====stdout====
+OpenAPI 3.x detected
+No errors or warnings found!
\ No newline at end of file
diff --git a/test-harness/scenarios/parameter-description-parameters.oas2.scenario b/test-harness/scenarios/parameter-description-parameters.oas2.scenario
new file mode 100644
index 00000000..5dbae55c
--- /dev/null
+++ b/test-harness/scenarios/parameter-description-parameters.oas2.scenario
@@ -0,0 +1,47 @@
+====test====
+Parameters - Parameter Objects - are validated to have description.
+====document====
+swagger: "2.0"
+paths:
+  /pets:
+    parameters:
+      - name: abc
+        type: string
+        in: header
+      - name: xyz
+        type: string
+        in: header
+        description: xyz
+    get:
+      parameters:
+        - name: ok
+          type: string
+          in: query
+        - name: abc
+          type: string
+          in: query
+          description: abc
+      responses:
+        200:
+          description: abc
+parameters:
+  skipParam:
+    name: skip
+    in: query
+    type: boolean
+  status:
+    name: status
+    in: query
+    type: string
+    description: status
+====command====
+lint {document} --ruleset ./test-harness/scenarios/rulesets/parameter-description.oas2.yaml
+====stdout====
+OpenAPI 2.0 (Swagger) detected
+
+{document}
+  5:9   warning  oas2-parameter-description  Parameter objects should have a `description`.
+ 14:11  warning  oas2-parameter-description  Parameter objects should have a `description`.
+ 25:13  warning  oas2-parameter-description  Parameter objects should have a `description`.
+
+✖ 3 problems (0 errors, 3 warnings, 0 infos)
\ No newline at end of file
diff --git a/test-harness/scenarios/parameter-description-parameters.oas3.scenario b/test-harness/scenarios/parameter-description-parameters.oas3.scenario
new file mode 100644
index 00000000..dbb212bf
--- /dev/null
+++ b/test-harness/scenarios/parameter-description-parameters.oas3.scenario
@@ -0,0 +1,34 @@
+====test====
+Parameters - Parameter Objects - are validated to have description.
+====document====
+openapi: 3.0.2
+paths:
+  /pets:
+    parameters:
+      - name: abc
+        in: body
+    get:
+      parameters:
+        - name: ok
+          in: header
+      responses:
+        200:
+          description: abc
+components:
+  parameters:
+    skipParam:
+      name: skip
+      in: query
+      schema:
+        type: integer
+====command====
+lint {document} --ruleset ./test-harness/scenarios/rulesets/parameter-description.oas3.yaml
+====stdout====
+OpenAPI 3.x detected
+
+{document}
+  5:9   warning  oas3-parameter-description  Parameter objects should have a `description`.
+  9:11  warning  oas3-parameter-description  Parameter objects should have a `description`.
+ 16:15  warning  oas3-parameter-description  Parameter objects should have a `description`.
+
+✖ 3 problems (0 errors, 3 warnings, 0 infos)
\ No newline at end of file
diff --git a/test-harness/scenarios/rulesets/parameter-description.oas2.yaml b/test-harness/scenarios/rulesets/parameter-description.oas2.yaml
new file mode 100644
index 00000000..dc82fa3a
--- /dev/null
+++ b/test-harness/scenarios/rulesets/parameter-description.oas2.yaml
@@ -0,0 +1,3 @@
+extends: [[spectral:oas2, off]]
+rules:
+  oas2-parameter-description: true
diff --git a/test-harness/scenarios/rulesets/parameter-description.oas3.yaml b/test-harness/scenarios/rulesets/parameter-description.oas3.yaml
new file mode 100644
index 00000000..dbc1f0fd
--- /dev/null
+++ b/test-harness/scenarios/rulesets/parameter-description.oas3.yaml
@@ -0,0 +1,3 @@
+extends: [[spectral:oas3, off]]
+rules:
+  oas3-parameter-description: true