Repository: ember-cli/eslint-plugin-ember
Pull request #1793 (2023-03-01 21:55:06)

[gjs] Fix bug with regex issues when parsing GLIMMER_TEMPLATE
another specific fix from #1659: when a lint error lands on the line containing `<template>`, we get an error like the following: 
```
SyntaxError: Invalid regular expression: /\b[__GLIMMER_TEMPLATE(`\b/: Unterminated character class
        at new RegExp (<anonymous>)
```

an easy way to repro this is to enable the `lines-between-class-members` rule, and put a `<template>` immediately after another class member.

I wrote a test, which currently is failing with the following message. This should just need some regexp escaping like Krystan mentioned in the original issue. will work on that shortly

```
 template tag failing test repro
    ✕ should break (12 ms)

  ● template tag failing test repro › should break

    SyntaxError: Invalid regular expression: /\b[__GLIMMER_TEMPLATE(`\b/: Unterminated character class
        at new RegExp (<anonymous>)

      123 |
      124 |     for (const [index, line] of originalLines.entries()) {
    > 125 |       const column = line.search(new RegExp(`\\b${token}\\b`));
          |                                  ^
      126 |       if (column > -1) {
      127 |         originalLineNumber = index + 1;
      128 |         originalColumnNumber = column + 1;

      at lib/preprocessors/glimmer.js:125:34
          at Array.map (<anonymous>)
      at map (lib/preprocessors/glimmer.js:106:20)
      at Linter._verifyWithProcessor (node_modules/eslint/lib/linter/linter.js:1906:16)
      at Linter._verifyWithConfigArray (node_modules/eslint/lib/linter/linter.js:1765:25)
      at Linter.verify (node_modules/eslint/lib/linter/linter.js:1479:65)
      at Linter.verifyAndFix (node_modules/eslint/lib/linter/linter.js:2031:29)
      at verifyText (node_modules/eslint/lib/cli-engine/cli-engine.js:245:48)
      at CLIEngine.executeOnText (node_modules/eslint/lib/cli-engine/cli-engine.js:917:26)
      at ESLint.lintText (node_modules/eslint/lib/eslint/eslint.js:593:23)
      at Object.lintText (tests/lib/rules-preprocessor/gjs-gts-processor-test.js:278:34)
```


Patch:
diff --git a/lib/preprocessors/glimmer.js b/lib/preprocessors/glimmer.js
index 86a6cad8..295f1aa9 100644
--- a/lib/preprocessors/glimmer.js
+++ b/lib/preprocessors/glimmer.js
@@ -9,6 +9,11 @@ const util = require('ember-template-imports/src/util');
 const TRANSFORM_CACHE = new Map();
 const TEXT_CACHE = new Map();
 
+// source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
+function escapeRegExp(string) {
+  return string.replace(/[$()*+.?[\\\]^{|}]/g, '\\$&'); // $& means the whole matched string
+}
+
 /**
  * This function is responsible for running the embedded templates transform
  * from ember-template-imports.
@@ -122,7 +127,7 @@ function mapRange(messages, filename) {
     let originalColumnNumber = 0;
 
     for (const [index, line] of originalLines.entries()) {
-      const column = line.search(new RegExp(`\\b${token}\\b`));
+      const column = line.search(new RegExp(`\\b${escapeRegExp(token)}\\b`));
       if (column > -1) {
         originalLineNumber = index + 1;
         originalColumnNumber = column + 1;


Test patch:
diff --git a/tests/lib/rules-preprocessor/gjs-gts-processor-test.js b/tests/lib/rules-preprocessor/gjs-gts-processor-test.js
index 2a953654..1bc27ba5 100644
--- a/tests/lib/rules-preprocessor/gjs-gts-processor-test.js
+++ b/tests/lib/rules-preprocessor/gjs-gts-processor-test.js
@@ -35,6 +35,7 @@ function initESLint(options) {
       plugins: ['ember'],
       extends: ['plugin:ember/recommended'],
       rules: {
+        'lines-between-class-members': 'error',
         'no-undef': 'error',
         'ember/no-get': 'off',
         'ember/no-array-prototype-extensions': 'error',
@@ -254,3 +255,30 @@ describe('line/col numbers should be correct', () => {
     });
   });
 });
+
+describe('lint errors on the exact line as the <template> tag', () => {
+  it('correctly outputs the lint error', async () => {
+    const eslint = initESLint();
+    const code = `
+    import Component from '@glimmer/component';
+
+    export default class MyComponent extends Component {
+      constructor() {
+        super(...arguments);
+      }
+
+      foo = 'bar';
+      <template>
+        <div>
+          some totally random, non-meaningful text 
+        </div>
+      </template>
+    }
+    `;
+    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });
+
+    const resultErrors = results.flatMap((result) => result.messages);
+    expect(resultErrors).toHaveLength(1);
+    expect(resultErrors[0].message).toBe('Expected blank line between class members.');
+  });
+});