Fix eslint rules

* import/named rule is used only for .js files - the rule doesn't work
  correctly with .ts files and ts compiler checks imports in .ts anyway.
* fix goog-module-id rule
* eslint now reports problems in .d.ts files

Change-Id: I95d4c1692f1aabdb460dc2e85a55ac5daae72ef0
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index cc9f304..932997e 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -149,7 +149,6 @@
         }
       }
     }],
-    "import/named": 2,
     "import/no-self-import": 2,
     // The no-cycle rule is slow, because it doesn't cache dependencies.
     // Disable it.
@@ -183,6 +182,7 @@
         // The rule is required for .js files only, because typescript compiler
         // always checks import.
         "import/no-unresolved": 2,
+        "import/named": 2,
       },
       "globals": {
         "goog": "readonly",
@@ -193,10 +193,10 @@
       "extends": [require.resolve("gts/.eslintrc.json")],
       "rules": {
         // The following rules is required to match internal google rules
-        "@typescript-eslint/restrict-plus-operands": "error"
+        "@typescript-eslint/restrict-plus-operands": "error",
       },
       "parserOptions": {
-        "project": path.resolve(__dirname, "./tsconfig.json"),
+        "project": path.resolve(__dirname, "./tsconfig_eslint.json"),
       }
     },
     {
@@ -209,6 +209,13 @@
       }
     },
     {
+      "files": ["**/*.d.ts"],
+      "rules": {
+        // See details in the //tools/js/eslint-rules/report-ts-error.js file.
+        "report-ts-error": "error",
+      }
+    },
+    {
       "files": ["*.html", "test.js", "test-infra.js", "template_test.js"],
       "rules": {
         "jsdoc/require-file-overview": "off"
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index fb2bd73..054033c 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -114,6 +114,8 @@
         ".eslintrc.js",
         ".prettierrc.js",
         ".eslint-ts-resolver.js",
+        "tsconfig_eslint.json",
+        # tsconfig_eslint.json extends tsconfig.json, pass it as a dependency
         "tsconfig.json",
     ],
     extensions = [
diff --git a/polygerrit-ui/app/tsconfig_eslint.json b/polygerrit-ui/app/tsconfig_eslint.json
new file mode 100644
index 0000000..7cc99c7
--- /dev/null
+++ b/polygerrit-ui/app/tsconfig_eslint.json
@@ -0,0 +1,11 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "skipLibCheck": false, /* This is required for report-ts-error.js.
+    See details in the //tools/js/eslint-rules/report-ts-error.js file.*/
+    "baseUrl": "../../external/ui_npm/node_modules" /* Only for bazel.
+    Compiler will try to use it to resolve module name and if it fail - will
+    fallback to a default behavior
+    (https://github.com/microsoft/TypeScript/issues/5039)*/
+  }
+}
diff --git a/tools/js/eslint-rules/goog-module-id.js b/tools/js/eslint-rules/goog-module-id.js
index 272e664..56cd645 100644
--- a/tools/js/eslint-rules/goog-module-id.js
+++ b/tools/js/eslint-rules/goog-module-id.js
@@ -106,7 +106,8 @@
     }
     const expectedName = 'polygerrit.' +
         filename.slice(index + pathStart.length, -jsExt.length)
-            .replace('/', '.');
+            .replace(/\//g, '.') // Replace all occurrences of '/' with '.'
+            .replace(/-/g, '$2d'); // Replace all occurrences of '-' with '$2d'
     if(argument.value !== expectedName) {
       context.report({
         message: `Invalid module id. It must be '${expectedName}'.`,
diff --git a/tools/js/eslint-rules/report-ts-error.js b/tools/js/eslint-rules/report-ts-error.js
new file mode 100644
index 0000000..48dddf4
--- /dev/null
+++ b/tools/js/eslint-rules/report-ts-error.js
@@ -0,0 +1,101 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// While we are migrating to typescript, gerrit can have .d.ts files.
+// The option "skipLibCheck" is set to true  In the tsconfig.json.
+// This is required, because we want to skip type checking in node_modules
+// directory - some .d.ts files in 3rd-party modules are incorrect.
+// Unfortunately, this options also excludes our own .d.ts files from type
+// checking. This rule reports all .ts errors in a file as tslint errors.
+
+function getMassageTextFromChain(chainNode, prefix) {
+  let nestedMessages = prefix + chainNode.messageText;
+  if (chainNode.next && chainNode.next.length > 0) {
+    nestedMessages += "\n";
+    for (const node of chainNode.next) {
+      nestedMessages +=
+          getMassageTextFromChain(node, prefix + " ");
+      if(!nestedMessages.endsWith('\n')) {
+        nestedMessages += "\n";
+      }
+    }
+  }
+  return nestedMessages;
+}
+
+function getMessageText(diagnostic) {
+  if (typeof diagnostic.messageText === 'string') {
+    return diagnostic.messageText;
+  }
+  return getMassageTextFromChain(diagnostic.messageText, "");
+}
+
+function getDiagnosticStartAndEnd(diagnostic) {
+  if(diagnostic.start) {
+    const file = diagnostic.file;
+    const start = file.getLineAndCharacterOfPosition(diagnostic.start);
+    const length = diagnostic.length ? diagnostic.length : 0;
+    return {
+      start,
+      end: file.getLineAndCharacterOfPosition(diagnostic.start + length),
+    };
+  }
+  return {
+    start: {line:0, character: 0},
+    end: {line:0, character: 0},
+  }
+}
+
+module.exports = {
+  meta: {
+    type: "problem",
+    docs: {
+      description: "Reports all typescript problems as linter problems",
+      category: ".d.ts",
+      recommended: false
+    },
+    schema: [],
+  },
+  create: function (context) {
+    const program = context.parserServices.program;
+    return {
+      Program: function(node) {
+        const sourceFile =
+            context.parserServices.esTreeNodeToTSNodeMap.get(node);
+        const allDiagnostics = [
+            ...program.getDeclarationDiagnostics(sourceFile),
+            ...program.getSemanticDiagnostics(sourceFile)];
+        for(const diagnostic of allDiagnostics) {
+          const {start, end } = getDiagnosticStartAndEnd(diagnostic);
+          context.report({
+            message: getMessageText(diagnostic),
+            loc: {
+              start: {
+                line: start.line + 1,
+                column: start.character,
+              },
+              end: {
+                line: end.line + 1,
+                column: end.character,
+              }
+            }
+          });
+        }
+      },
+    };
+  }
+};