Repository: paustint/soql-parser-js
Pull request #116 (2020-08-24 02:03:18)

typeof query ignored in getFlattenedFields() #115
Updated getFlattenedFields() to handle typeof
added unit test
fixed security issues with non-library code

resolves #1115


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adc6695..87db08d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Changelog
 
+## 2.5.5
+
+Aug 23, 2020
+
+1. `getFlattenedFields` ignores `typeof` clauses in query. (#115)
+
+## 2.5.4
+
+April 12, 2020
+
+1. `getFlattenedFields` returns incorrect results if relationship field is grouped and you are grouping for only one field (#113)
+
 ## 2.5.3
 
 April 24, 2020
diff --git a/package-lock.json b/package-lock.json
index 7e32f6f..318fac2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1006,9 +1006,9 @@
       "dev": true
     },
     "cacache": {
-      "version": "12.0.3",
-      "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
-      "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
+      "version": "12.0.4",
+      "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+      "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
       "dev": true,
       "requires": {
         "bluebird": "^3.5.5",
@@ -1410,6 +1410,12 @@
         "delayed-stream": "~1.0.0"
       }
     },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true
+    },
     "commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -1827,9 +1833,9 @@
       "dev": true
     },
     "dot-prop": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
-      "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
+      "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
       "dev": true,
       "requires": {
         "is-obj": "^1.0.0"
@@ -1854,9 +1860,9 @@
       }
     },
     "elliptic": {
-      "version": "6.5.2",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
-      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+      "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
       "dev": true,
       "requires": {
         "bn.js": "^4.4.0",
@@ -2212,9 +2218,9 @@
       }
     },
     "figgy-pudding": {
-      "version": "3.5.1",
-      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
-      "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+      "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
       "dev": true
     },
     "figures": {
@@ -3940,9 +3946,9 @@
       }
     },
     "lodash": {
-      "version": "4.17.15",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
-      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+      "version": "4.17.20",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+      "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
       "dev": true
     },
     "lodash.find": {
@@ -5080,6 +5086,12 @@
       "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
       "dev": true
     },
+    "prettier": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
+      "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
+      "dev": true
+    },
     "process": {
       "version": "0.11.10",
       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -5467,6 +5479,12 @@
             "p-locate": "^4.1.0"
           }
         },
+        "lodash": {
+          "version": "4.17.15",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+          "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+          "dev": true
+        },
         "ms": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5751,10 +5769,13 @@
       }
     },
     "serialize-javascript": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
-      "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
-      "dev": true
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+      "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.1.0"
+      }
     },
     "set-blocking": {
       "version": "2.0.0",
@@ -6295,9 +6316,9 @@
       }
     },
     "terser": {
-      "version": "4.6.7",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz",
-      "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==",
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+      "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
       "dev": true,
       "requires": {
         "commander": "^2.20.0",
@@ -6305,16 +6326,10 @@
         "source-map-support": "~0.5.12"
       },
       "dependencies": {
-        "commander": {
-          "version": "2.20.3",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-          "dev": true
-        },
         "source-map-support": {
-          "version": "0.5.16",
-          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
-          "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
+          "version": "0.5.19",
+          "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+          "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
           "dev": true,
           "requires": {
             "buffer-from": "^1.0.0",
@@ -6324,16 +6339,16 @@
       }
     },
     "terser-webpack-plugin": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
-      "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
+      "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
       "dev": true,
       "requires": {
         "cacache": "^12.0.2",
         "find-cache-dir": "^2.1.0",
         "is-wsl": "^1.1.0",
         "schema-utils": "^1.0.0",
-        "serialize-javascript": "^2.1.2",
+        "serialize-javascript": "^4.0.0",
         "source-map": "^0.6.1",
         "terser": "^4.1.2",
         "webpack-sources": "^1.4.0",
diff --git a/package.json b/package.json
index 4671596..f8bfb5f 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
     "license-webpack-plugin": "^2.1.2",
     "mocha": "^7.1.1",
     "npm-run-all": "^4.1.5",
+    "prettier": "^2.0.5",
     "release-it": "^12.4.1",
     "string-replace-loader": "^2.2.0",
     "ts-node": "^7.0.1",
diff --git a/src/api/public-utils.ts b/src/api/public-utils.ts
index 53c2e2e..c7aefd0 100644
--- a/src/api/public-utils.ts
+++ b/src/api/public-utils.ts
@@ -125,8 +125,8 @@ export function getFlattenedFields(
   const fields = query.fields;
   // if a relationship field is used in a group by, then Salesforce removes the relationship portion of the field in the returned records
   let groupByFields: { [field: string]: string } = {};
-  if(!!query.groupBy && query.groupBy.field) {
-    if(!Array.isArray(query.groupBy.field)) {
+  if (!!query.groupBy && query.groupBy.field) {
+    if (!Array.isArray(query.groupBy.field)) {
       query.groupBy.field = [query.groupBy.field];
     }
     groupByFields = query.groupBy.field.reduce((output: { [field: string]: string }, field) => {
@@ -139,7 +139,7 @@ export function getFlattenedFields(
   let sObjectAlias = (query.sObjectAlias || '').toLowerCase();
 
   const parsedFields = fields
-    .map(field => {
+    .flatMap(field => {
       switch (field.type) {
         case 'Field': {
           return field.field;
@@ -197,6 +197,21 @@ export function getFlattenedFields(
         case 'FieldSubquery': {
           return field.subquery.relationshipName;
         }
+        case 'FieldTypeof': {
+          // keep track of fields to avoid adding duplicates
+          const priorFields = new Set<string>();
+          const fields: string[] = [];
+          // Add all unique fields across all conditions
+          field.conditions.forEach(condition => {
+            condition.fieldList.forEach(currField => {
+              if (!priorFields.has(currField)) {
+                priorFields.add(currField);
+                fields.push(`${field.field}.${currField}`);
+              }
+            });
+          });
+          return fields;
+        }
         default:
           break;
       }


Test patch:
diff --git a/test/public-utils-test-data.ts b/test/public-utils-test-data.ts
index 8b52a60..c6dc753 100644
--- a/test/public-utils-test-data.ts
+++ b/test/public-utils-test-data.ts
@@ -349,32 +349,28 @@ export const testCases: FlattenedObjTestCase[] = [
   },
   {
     testCase: 11,
-    expectedFields: ['Name','expr0'],
+    expectedFields: ['Name', 'expr0'],
     query: {
       fields: [
         {
-          type: "FieldRelationship",
-          field: "Name",
-          relationships: [
-            "Account"
-          ],
-          rawValue: "Account.Name"
+          type: 'FieldRelationship',
+          field: 'Name',
+          relationships: ['Account'],
+          rawValue: 'Account.Name',
         },
         {
-          type: "FieldFunctionExpression",
-          functionName: "AVG",
-          parameters: [
-            "Amount"
-          ],
+          type: 'FieldFunctionExpression',
+          functionName: 'AVG',
+          parameters: ['Amount'],
           isAggregateFn: true,
-          rawValue: "AVG(Amount)"
-        }
+          rawValue: 'AVG(Amount)',
+        },
       ],
-      sObject: "Opportunity",
+      sObject: 'Opportunity',
       groupBy: {
-        field: "Account.Name"
+        field: 'Account.Name',
       },
-      limit: 5
+      limit: 5,
     },
     sfdcObj: {
       expr0: 1,
@@ -382,4 +378,40 @@ export const testCases: FlattenedObjTestCase[] = [
       SBQQ__Quote__c: 'a1j50000004BBOmAAO',
     },
   },
+  {
+    testCase: 12,
+    expectedFields: ['Id', 'What.Id', 'What.Phone', 'What.Name'],
+    query: {
+      fields: [
+        {
+          type: 'Field',
+          field: 'Id',
+        },
+        {
+          type: 'FieldTypeof',
+          field: 'What',
+          conditions: [
+            {
+              type: 'WHEN',
+              objectType: 'Account',
+              fieldList: ['Id', 'Phone'],
+            },
+            {
+              type: 'ELSE',
+              fieldList: ['Id', 'Name'],
+            },
+          ],
+        },
+      ],
+      sObject: 'Event',
+    },
+    sfdcObj: {
+      Id: '00U6g000008goSXEAY',
+      What: {
+        Id: '5006g00000Bt97JAAR',
+        Phone: '(312) 596-1000',
+        Name: '00001016',
+      },
+    },
+  },
 ];