From 1bd868a45479d2f9e9230d14c6b40a4f5cd23b06 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 8 Nov 2023 23:30:48 +0100 Subject: [PATCH] reference tag name parts (split up with dot) instead of whole tag --- lib/parsers/gjs-gts-parser.js | 40 +++++++++++++-- package.json | 1 + .../rules-preprocessor/gjs-gts-parser-test.js | 49 ++++++++++++++++--- yarn.lock | 5 ++ 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/lib/parsers/gjs-gts-parser.js b/lib/parsers/gjs-gts-parser.js index 1d437cc1d3..695db96033 100644 --- a/lib/parsers/gjs-gts-parser.js +++ b/lib/parsers/gjs-gts-parser.js @@ -7,6 +7,7 @@ const typescriptParser = require('@typescript-eslint/parser'); const TypescriptScope = require('@typescript-eslint/scope-manager'); const { Reference, Scope, Variable, Definition } = require('eslint-scope'); const { registerParsedFile } = require('../preprocessors/noop'); +const htmlTags = require('html-tags'); /** * finds the nearest node scope @@ -260,6 +261,34 @@ function preprocessGlimmerTemplates(info, code) { n.loc.start = codeLines.offsetToPosition(tpl.templateRange[0]); n.loc.end = codeLines.offsetToPosition(tpl.templateRange[1]); } + // split up element node into sub nodes to be able to reference tag name + // parts -> nodes for `Foo` and `Bar` + if (n.type === 'ElementNode') { + n.name = n.tag; + n.parts = []; + let start = n.range[0]; + let codeSlice = code.slice(...n.range); + for (const part of n.tag.split('.')) { + const regex = new RegExp(`\\b${part}\\b`); + const match = codeSlice.match(regex); + const range = [start + match.index, 0]; + range[1] = range[0] + part.length; + codeSlice = code.slice(range[1], n.range[1]); + start = range[1]; + n.parts.push({ + type: 'GlimmerElementNodePart', + name: part, + range, + parent: n, + loc: { + start: codeLines.offsetToPosition(range[0]), + end: codeLines.offsetToPosition(range[1]), + }, + }); + } + } + // block params do not have location information + // add our own nodes so we can reference them if ('blockParams' in n) { n.params = []; } @@ -389,10 +418,13 @@ function convertAst(result, preprocessedResult, visitorKeys) { } } if (node.type === 'GlimmerElementNode') { - node.name = node.tag; - const { scope, variable } = findVarInParentScopes(result.scopeManager, path, node.tag) || {}; - if (scope && (variable || isUpperCase(node.tag[0]))) { - registerNodeInScope(node, scope, variable); + // always reference first part of tag name, this also has the advantage + // that errors regarding this tag will only mark the tag name instead of + // the whole tag + children + const n = node.parts[0]; + const { scope, variable } = findVarInParentScopes(result.scopeManager, path, n.name) || {}; + if (scope && (variable || isUpperCase(n.name[0]) || node.name.includes('.') || !htmlTags.includes(node.name))) { + registerNodeInScope(n, scope, variable); } } diff --git a/package.json b/package.json index fb4d026a6a..90778622f5 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "eslint-scope": "^7.2.2", "eslint-utils": "^3.0.0", "estraverse": "^5.3.0", + "html-tags": "^3.3.1", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "requireindex": "^1.2.0", diff --git a/tests/lib/rules-preprocessor/gjs-gts-parser-test.js b/tests/lib/rules-preprocessor/gjs-gts-parser-test.js index cbe3f2a2c2..3ced0f3ecb 100644 --- a/tests/lib/rules-preprocessor/gjs-gts-parser-test.js +++ b/tests/lib/rules-preprocessor/gjs-gts-parser-test.js @@ -257,6 +257,8 @@ const invalid = [ {{#let 'x' as |noop notUsed usedEl|}} {{noop}} + + {{/let}} `, @@ -272,6 +274,28 @@ const invalid = [ ruleId: 'no-unused-vars', severity: 2, }, + { + column: 10, + endColumn: 15, + endLine: 6, + line: 6, + message: "'undef' is not defined.", + messageId: 'undef', + nodeType: 'GlimmerElementNodePart', + ruleId: 'no-undef', + severity: 2, + }, + { + column: 10, + endColumn: 26, + endLine: 7, + line: 7, + message: "'non-std-html-tag' is not defined.", + messageId: 'undef', + nodeType: 'GlimmerElementNodePart', + ruleId: 'no-undef', + severity: 2, + }, ], }, { @@ -279,14 +303,25 @@ const invalid = [ code: ` `, errors: [ { message: "'Foo' is not defined.", line: 3, - column: 9, - endColumn: 16, + endLine: 3, + column: 10, + endColumn: 13, + }, + { + message: "'Bar' is not defined.", + line: 4, + endLine: 4, + column: 10, + endColumn: 13, }, ], }, @@ -301,8 +336,8 @@ const invalid = [ { message: "'F_0_O' is not defined.", line: 3, - column: 9, - endColumn: 18, + column: 10, + endColumn: 15, }, ], }, @@ -681,13 +716,13 @@ describe('multiple tokens in same file', () => { }); expect(resultErrors[1]).toStrictEqual({ - column: 30, - endColumn: 37, + column: 31, + endColumn: 34, endLine: 4, line: 4, message: "'Bad' is not defined.", messageId: 'undef', - nodeType: 'GlimmerElementNode', + nodeType: 'GlimmerElementNodePart', ruleId: 'no-undef', severity: 2, }); diff --git a/yarn.lock b/yarn.lock index 6249e6b749..7635799ce3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3471,6 +3471,11 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"