Repository: oppia/oppia

Issue:
Convert "throw <string>" to "throw <Error>"
We throw string instead of Error object in a bunch of places in our code base. This causes issues when trying to capture frontend errors in stackdriver because we expect Error object properties such as `message` and `stack` (See App.ts). Also in general, it is good practise to avoid throwing primitive data types. 

Example of the change:
Replace
```ts
throw 'Invalid length for list editor: ' + $scope.len;
```

with
```ts
throw new Error('Invalid length for list editor: ' + $scope.len);
```


Discussion:
Can i work on this
@abhayraghuwanshi, sure! I've assigned this issue to you.
@abhayraghuwanshi are you working on this issue?
yes

sorry I was busy last night was not able to reply to that

No problem. Thanks for letting me know!
can I still work in that or is it closed now
It is open. Please feel free to raise a PR once you are done with your changes. Thanks!
@kevintab95  can you assign changelog to Pr

Resolving patch:
diff --git a/scripts/linters/general_purpose_linter.py b/scripts/linters/general_purpose_linter.py
--- a/scripts/linters/general_purpose_linter.py
+++ b/scripts/linters/general_purpose_linter.py
@@ -174,6 +174,35 @@
             'extensions/value_generators/',
             'extensions/visualizations/')
     },
+    {
+        'regexp': re.compile(r'tothrow[(]'),
+        'message': 'Please use \'toThrowError\' instead of '
+                   '\'toThrow\'',
+        'excluded_files': (
+            # Note to developers: In the excluded_files below,
+            # we use custom errors which cannot be caught by regex.
+            # The Logic Proof interaction which uses these custom errors
+            # will be deprecated soon (see #9198).
+            'extensions/interactions/LogicProof/static/js/student.spec.ts',
+            'extensions/interactions/LogicProof/static/js/complete.spec.ts',
+            'extensions/interactions/LogicProof/static/js/teacher.spec.ts'),
+        'excluded_dirs': ()
+    },
+    {
+        'regexp': re.compile(r'(?!catch\s(\n|.)*throw\s\w;\n.*})'
+                             r'throw\s\b(\bError|\bTypeError|\bRangeError'
+                             r'\bSyntaxError|\bDimensionError)\('),
+        'message': 'Please use \'throw new\' instead of \'throw\'',
+        'excluded_files': (),
+        'excluded_dirs': ()
+    },
+    {
+        'regexp': re.compile(r'(?!catch\s(\n|.)*throw\s\w;\n.*})throw\s*\''),
+        'message': 'Please use '
+                   '\'throw new Error\' instead of \'throw\'',
+        'excluded_files': (),
+        'excluded_dirs': ()
+    },
     {
         'regexp': re.compile(r'\$parent'),
         'message': 'Please do not access parent properties ' +


Test patch:
diff --git a/core/tests/build_sources/templates/pages/Base.ts b/core/tests/build_sources/templates/pages/Base.ts
--- a/core/tests/build_sources/templates/pages/Base.ts
+++ b/core/tests/build_sources/templates/pages/Base.ts
@@ -51,7 +51,7 @@ angular.module('oppia').controller('Base', [
       var mainContentElement = document.getElementById('oppia-main-content');
 
       if (!mainContentElement) {
-        throw Error('Variable mainContentElement is undefined.');
+        throw new Error('Variable mainContentElement is undefined.');
       }
       mainContentElement.tabIndex = -1;
       mainContentElement.scrollIntoView();
diff --git a/core/tests/protractor.conf.js b/core/tests/protractor.conf.js
--- a/core/tests/protractor.conf.js
+++ b/core/tests/protractor.conf.js
@@ -192,7 +192,7 @@ exports.config = {
           } else {
             errorMessage = pattern + ' does not exist.';
           }
-          throw Error(errorMessage)
+          throw new Error(errorMessage)
         }
       }
     });
diff --git a/core/tests/protractor_utils/AdminPage.js b/core/tests/protractor_utils/AdminPage.js
--- a/core/tests/protractor_utils/AdminPage.js
+++ b/core/tests/protractor_utils/AdminPage.js
@@ -148,7 +148,7 @@ var AdminPage = function() {
         success = success || results[i];
       }
       if (!success) {
-        throw Error('Could not find config property: ' + propertyName);
+        throw new Error('Could not find config property: ' + propertyName);
       }
     });
   };
diff --git a/core/tests/protractor_utils/CollectionEditorPage.js b/core/tests/protractor_utils/CollectionEditorPage.js
--- a/core/tests/protractor_utils/CollectionEditorPage.js
+++ b/core/tests/protractor_utils/CollectionEditorPage.js
@@ -91,7 +91,7 @@ var CollectionEditorPage = function() {
       if (isEnabled) {
         addExplorationButton.click();
       } else {
-        throw Error ('Add Exploration Button is not clickable');
+        throw new Error ('Add Exploration Button is not clickable');
       }
     });
   };
diff --git a/core/tests/protractor_utils/CreatorDashboardPage.js b/core/tests/protractor_utils/CreatorDashboardPage.js
--- a/core/tests/protractor_utils/CreatorDashboardPage.js
+++ b/core/tests/protractor_utils/CreatorDashboardPage.js
@@ -134,7 +134,8 @@ var CreatorDashboardPage = function() {
   this.editExploration = function(explorationTitle) {
     _getExplorationElements(explorationTitle).then(function(elems) {
       if (elems.length === 0) {
-        throw 'Could not find exploration tile with name ' + explorationTitle;
+        throw new Error(
+          'Could not find exploration tile with name ' + explorationTitle);
       }
       var explorationElement = elems[0].element(
         by.css('.protractor-test-title-mask'));
@@ -257,7 +258,8 @@ var CreatorDashboardPage = function() {
   this.expectToHaveExplorationCard = function(explorationName) {
     _getExplorationElements(explorationName).then(function(elems) {
       if (elems.length === 0) {
-        throw 'Could not find exploration title with name ' + explorationTitle;
+        throw new Error(
+          'Could not find exploration title with name ' + explorationTitle);
       }
       expect(elems.length).toBeGreaterThanOrEqual(1);
     });
diff --git a/core/tests/protractor_utils/ExplorationEditorHistoryTab.js b/core/tests/protractor_utils/ExplorationEditorHistoryTab.js
--- a/core/tests/protractor_utils/ExplorationEditorHistoryTab.js
+++ b/core/tests/protractor_utils/ExplorationEditorHistoryTab.js
@@ -70,7 +70,7 @@ var ExplorationEditorHistoryTab = function() {
             }
           }
           if (!matched) {
-            throw Error(
+            throw new Error(
               'State ' + stateName + ' not found by getHistoryGraph.');
           }
         });
diff --git a/core/tests/protractor_utils/ExplorationEditorMainTab.js b/core/tests/protractor_utils/ExplorationEditorMainTab.js
--- a/core/tests/protractor_utils/ExplorationEditorMainTab.js
+++ b/core/tests/protractor_utils/ExplorationEditorMainTab.js
@@ -181,7 +181,8 @@ var ExplorationEditorMainTab = function() {
       if (buttons.length === 1) {
         buttons[0].click();
       } else if (buttons.length !== 0) {
-        throw 'Expected to find at most one \'exit tutorial\' button';
+        throw new Error(
+          'Expected to find at most one \'exit tutorial\' button');
       }
     });
   };
@@ -196,7 +197,7 @@ var ExplorationEditorMainTab = function() {
       if (buttons.length === 1) {
         buttons[0].click();
       } else {
-        throw Error('There is more than 1 Finish button!');
+        throw new Error('There is more than 1 Finish button!');
       }
     });
   };
@@ -227,7 +228,7 @@ var ExplorationEditorMainTab = function() {
             tutorialTabHeadingElement,
             'Tutorial stage takes too long to disappear');
         } else {
-          throw Error('There is more than one Next button!');
+          throw new Error('There is more than one Next button!');
         }
       });
     });
@@ -844,10 +845,10 @@ var ExplorationEditorMainTab = function() {
       if (ruleTemplates[interactionId].hasOwnProperty(ruleName)) {
         return ruleTemplates[interactionId][ruleName].description;
       } else {
-        throw Error('Unknown rule: ' + ruleName);
+        throw new Error('Unknown rule: ' + ruleName);
       }
     } else {
-      throw Error('Could not find rules for interaction: ' + interactionId);
+      throw new Error('Could not find rules for interaction: ' + interactionId);
     }
   };
 
@@ -928,9 +929,10 @@ var ExplorationEditorMainTab = function() {
           ruleDescription = ruleDescription.replace(placeholderElement, '...');
         } else {
           if (providedText.length !== placeholders.length) {
-            throw Error('# of feedback text(' + textArray.length +
-            ') is expected to match # of placeholders(' +
-            (placeholders.length) + ')');
+            throw new Error(
+              '# of feedback text(' + textArray.length +
+              ') is expected to match # of placeholders(' +
+              (placeholders.length) + ')');
           }
           ruleDescription = ruleDescription.replace(
             placeholderElement, providedText[index].toString());
@@ -1000,7 +1002,7 @@ var ExplorationEditorMainTab = function() {
         }
       }
       if (!matched) {
-        throw Error(
+        throw new Error(
           'State ' + targetName + ' not found by editorMainTab.moveToState.');
       }
     });
diff --git a/core/tests/protractor_utils/ExplorationEditorTranslationTab.js b/core/tests/protractor_utils/ExplorationEditorTranslationTab.js
--- a/core/tests/protractor_utils/ExplorationEditorTranslationTab.js
+++ b/core/tests/protractor_utils/ExplorationEditorTranslationTab.js
@@ -48,7 +48,8 @@ var ExplorationEditorTranslationTab = function() {
       if (buttons.length === 1) {
         buttons[0].click();
       } else if (buttons.length !== 0) {
-        throw 'Expected to find at most one \'exit tutorial\' button';
+        throw new Error(
+          'Expected to find at most one \'exit tutorial\' button');
       }
     });
   };
@@ -63,7 +64,7 @@ var ExplorationEditorTranslationTab = function() {
       if (buttons.length === 1) {
         buttons[0].click();
       } else {
-        throw Error('There is more than 1 Finish button!');
+        throw new Error('There is more than 1 Finish button!');
       }
     });
   };
@@ -94,7 +95,7 @@ var ExplorationEditorTranslationTab = function() {
             tutorialTabHeadingElement,
             'Tutorial stage takes too long to disappear');
         } else {
-          throw Error('There is more than one Next button!');
+          throw new Error('There is more than one Next button!');
         }
       });
     });
@@ -530,7 +531,7 @@ var ExplorationEditorTranslationTab = function() {
         }
       }
       if (!matched) {
-        throw Error(
+        throw new Error(
           'State ' + targetName + ' not found by editorTranslationTab.' +
           'moveToState.');
       }
@@ -550,7 +551,7 @@ var ExplorationEditorTranslationTab = function() {
         }
       }
       if (!matched) {
-        throw Error(
+        throw new Error(
           'State ' + targetName +
           ' not found by editorTranslationTab.expectCorrectStatusColor.');
       }
diff --git a/core/tests/protractor_utils/GetStartedPage.js b/core/tests/protractor_utils/GetStartedPage.js
--- a/core/tests/protractor_utils/GetStartedPage.js
+++ b/core/tests/protractor_utils/GetStartedPage.js
@@ -34,7 +34,7 @@ var GetStartedPage = function() {
     } else if (type === 'name') {
       var tag = element(by.css('meta[name="' + name + '"]'));
     } else {
-      throw Error('Unsupported tag type specified: ' + type);
+      throw new Error('Unsupported tag type specified: ' + type);
     }
 
     return tag.getAttribute('content');
diff --git a/core/tests/protractor_utils/ProfilePage.js b/core/tests/protractor_utils/ProfilePage.js
--- a/core/tests/protractor_utils/ProfilePage.js
+++ b/core/tests/protractor_utils/ProfilePage.js
@@ -81,7 +81,8 @@ var ProfilePage = function() {
   this.expectToHaveExplorationCards = function() {
     allExplorationCardElements.then(function(cards) {
       if (cards.length === 0) {
-        throw 'There is no exploration card on this profile';
+        throw new Error(
+          'There is no exploration card on this profile');
       }
       expect(cards.length).toBeGreaterThan(0);
     });
@@ -98,7 +99,8 @@ var ProfilePage = function() {
       });
 
     if (explorationsCardByName.length === 0) {
-      throw 'There is no exploration card with name ' + explorationName;
+      throw new Error(
+        'There is no exploration card with name ' + explorationName);
     }
     expect(explorationsCardByName.count()).toBeGreaterThanOrEqual(1);
   };
diff --git a/core/tests/protractor_utils/TopicsAndSkillsDashboardPage.js b/core/tests/protractor_utils/TopicsAndSkillsDashboardPage.js
--- a/core/tests/protractor_utils/TopicsAndSkillsDashboardPage.js
+++ b/core/tests/protractor_utils/TopicsAndSkillsDashboardPage.js
@@ -271,7 +271,7 @@ var TopicsAndSkillsDashboardPage = function() {
       topicsTabButton, 'Unable to click on topics tab.');
     _getTopicElements(topicName).then(function(topicElements) {
       if (topicElements.length === 0) {
-        throw 'Could not find topic tile with name ' + topicName;
+        throw new Error('Could not find topic tile with name ' + topicName);
       }
       waitFor.elementToBeClickable(
         topicElements[0], 'Unable to click on topic: ' + topicName);
diff --git a/core/tests/protractor_utils/forms.js b/core/tests/protractor_utils/forms.js
--- a/core/tests/protractor_utils/forms.js
+++ b/core/tests/protractor_utils/forms.js
@@ -35,7 +35,7 @@ var DictionaryEditor = function(elem) {
 
 var GraphEditor = function(graphInputContainer) {
   if (!graphInputContainer) {
-    throw Error('Please provide Graph Input Container element');
+    throw new Error('Please provide Graph Input Container element');
   }
   var vertexElement = function(index) {
     // Would throw incorrect element error if provided incorrect index number.
@@ -598,7 +598,7 @@ var CodeMirrorChecker = function(elem, codeMirrorPaneToScroll) {
             return lineNumber;
           }
           if (!compareDict.hasOwnProperty(lineNumber)) {
-            throw Error('Line ' + lineNumber + ' not found in CodeMirror');
+            throw new Error('Line ' + lineNumber + ' not found in CodeMirror');
           }
           expect(lineElement.element(by.xpath('./pre')).getText())
             .toEqual(compareDict[lineNumber].text);
@@ -619,7 +619,8 @@ var CodeMirrorChecker = function(elem, codeMirrorPaneToScroll) {
       } else {
         for (var lineNumber in compareDict) {
           if (compareDict[lineNumber].checked !== true) {
-            throw Error('Expected line ' + lineNumber + ': \'' +
+            throw new Error(
+              'Expected line ' + lineNumber + ': \'' +
               compareDict[lineNumber].text + '\' to be found in CodeMirror');
           }
         }
@@ -662,7 +663,7 @@ var CodeMirrorChecker = function(elem, codeMirrorPaneToScroll) {
         var lineNumber = textArray[i];
         var lineText = textArray[i + 1];
         if (!compareDict.hasOwnProperty(lineNumber)) {
-          throw Error('Line ' + lineNumber + ' not found in CodeMirror');
+          throw new Error('Line ' + lineNumber + ' not found in CodeMirror');
         }
         expect(lineText).toEqual(compareDict[lineNumber].text);
         compareDict[lineNumber].checked = true;
@@ -676,7 +677,8 @@ var CodeMirrorChecker = function(elem, codeMirrorPaneToScroll) {
       } else {
         for (var dictLineNumber in compareDict) {
           if (compareDict[dictLineNumber].checked !== true) {
-            throw Error('Expected line ' + lineNumber + ': \'' +
+            throw new Error(
+              'Expected line ' + lineNumber + ': \'' +
               compareDict[dictLineNumber].text + '\' to be found in CodeMirror'
             );
           }
@@ -748,7 +750,7 @@ var getEditor = function(formName) {
   } else if (objects.OBJECT_EDITORS.hasOwnProperty(formName)) {
     return objects.OBJECT_EDITORS[formName];
   } else {
-    throw Error('Unknown form / object requested: ' + formName);
+    throw new Error('Unknown form / object requested: ' + formName);
   }
 };