Repository: BRIKEV/express-jsdoc-swagger
Pull request #227 (2022-07-16 19:11:20)

feat: add support for dictionary primitives
### What kind of change does this PR introduce? (check at least one)
 - [ ] Bugfix
 - [x] Feature
 - [ ] Test
 - [ ] Docs
 - [ ] Refactor
 - [ ] Build-related changes
 - [ ] Other, please describe:

### Description:

Add support for using primitive values with `Dictionary` special type. For example to represent object with any number of string keys and string values:

```
{
  "en": "English",
  "fr": "French"
}
```

Official documentation on dictionaries in OpenAPI:
https://swagger.io/docs/specification/data-models/dictionaries/

While `object` is technically a primitive value based on `TYPES` in `transforms/utils/validateTypes.js`, users of this library will want to use `typedef` to represent objects.

For what it's worth, normally in JSDoc [dictionary/record](https://jsdoc.app/tags-type.html) type objects are represented as:

```
Object.<string,string>
```

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=BRIKEV_express-jsdoc-swagger&pullRequest=227)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=BRIKEV_express-jsdoc-swagger&pullRequest=227) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=BRIKEV_express-jsdoc-swagger&pullRequest=227&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..1a158fb
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,21 @@
+{
+  // Use IntelliSense to learn about possible attributes.
+  // Hover to view descriptions of existing attributes.
+  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Debug Jest Tests",
+      "type": "node",
+      "request": "launch",
+      "runtimeArgs": [
+        "--inspect-brk",
+        "${workspaceRoot}/node_modules/.bin/jest",
+        "--runInBand"
+      ],
+      "console": "integratedTerminal",
+      "internalConsoleOptions": "neverOpen",
+      "port": 9229
+    }
+  ]
+}
diff --git a/transforms/components/index.js b/transforms/components/index.js
index ecbe66f..174b342 100644
--- a/transforms/components/index.js
+++ b/transforms/components/index.js
@@ -4,6 +4,7 @@ const { refSchema, formatRefSchema } = require('../utils/refSchema');
 const addEnumValues = require('../utils/enumValues');
 const formatDescription = require('../utils/formatDescription');
 const combineSchema = require('../utils/combineSchema');
+const validateTypes = require('../utils/validateTypes');
 
 const REQUIRED = 'required';
 
@@ -65,15 +66,21 @@ const getRequiredProperties = properties => (
 const formatRequiredProperties = requiredProperties => requiredProperties.map(getPropertyName);
 
 const addDictionaryAdditionalProperties = typedef => {
-  if (!typedef.type.expression || typedef.type.expression.name !== 'Dictionary') {
-    return {};
+  if (
+    typedef.type.expression
+    && typedef.type.expression.name === 'Dictionary'
+  ) {
+    const typeName = typedef.type.applications[0].name;
+    const isPrimitive = validateTypes(typeName);
+
+    return {
+      additionalProperties: {
+        ...(isPrimitive ? { type: typeName } : { $ref: `#/components/schemas/${typeName}` }),
+      },
+    };
   }
 
-  return {
-    additionalProperties: {
-      $ref: `#/components/schemas/${typedef.type.applications[0].name}`,
-    },
-  };
+  return {};
 };
 
 const parseSchema = (schema, options = {}) => {


Test patch:
diff --git a/test/transforms/components/index.test.js b/test/transforms/components/index.test.js
index 633a960..d2bb6e4 100644
--- a/test/transforms/components/index.test.js
+++ b/test/transforms/components/index.test.js
@@ -909,4 +909,30 @@ describe('parseComponents method', () => {
     const result = parseComponents({}, parsedJSDocs);
     expect(result).toEqual(expected);
   });
+
+  it('Should parse jsdoc component spec record type', () => {
+    const jsodInput = [`
+      /**
+      * Records dict
+      * @typedef {Dictionary<string>} Records map
+      */
+    `];
+    const expected = {
+      components: {
+        schemas: {
+          Records: {
+            type: 'object',
+            description: 'Records dict',
+            properties: {},
+            additionalProperties: {
+              type: 'string',
+            },
+          },
+        },
+      },
+    };
+    const parsedJSDocs = jsdocInfo()(jsodInput);
+    const result = parseComponents({}, parsedJSDocs);
+    expect(result).toEqual(expected);
+  });
 });