From aab507f60223ec0130753dbaf817523c4646f699 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:08:03 +0100 Subject: [PATCH] feat: emit position info in diagnostics Adds `LINE:COL` to messages which can support it. --- package-lock.json | 98 +++++++++---------- package.json | 4 +- src/analyze/web-features-codemods.ts | 21 ++-- src/commands/analyze.ts | 16 +-- .../web-features-codemods.test.ts.snap | 64 +++++++++++- src/types.ts | 11 +++ 6 files changed, 141 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73530e7..9f761e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@clack/prompts": "^1.7.0", - "@e18e/web-features-codemods": "^0.2.1", + "@e18e/web-features-codemods": "^0.3.0", "@publint/pack": "^0.1.5", "core-js-compat": "^3.48.0", "enginematch": "^0.2.0", @@ -19,7 +19,7 @@ "gunshi": "^0.35.1", "lockparse": "^0.5.2", "module-replacements": "^3.0.0", - "module-replacements-codemods": "^2.0.0", + "module-replacements-codemods": "^2.0.1", "obug": "^2.1.3", "package-manager-detector": "^1.7.0", "publint": "^0.3.21", @@ -45,29 +45,29 @@ } }, "node_modules/@ast-grep/napi": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi/-/napi-0.43.0.tgz", - "integrity": "sha512-dPugC04xwgk3gJl17jJvTjVjx1IXtWI9EcEQ0szlh0jg1UfvGDdZJo9/S9Pk7gzGBNd2bW8hIL3dG6K4T8PEJA==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi/-/napi-0.44.1.tgz", + "integrity": "sha512-dJAJVYRKmUjXn+4mXUgfRvMrWujB9mLzPSq/2e8J7n6Hn3dY4jpZNpChj6hMi7PlZ2J2c7coAVoDBaax06uvXw==", "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@ast-grep/napi-darwin-arm64": "0.43.0", - "@ast-grep/napi-darwin-x64": "0.43.0", - "@ast-grep/napi-linux-arm64-gnu": "0.43.0", - "@ast-grep/napi-linux-arm64-musl": "0.43.0", - "@ast-grep/napi-linux-x64-gnu": "0.43.0", - "@ast-grep/napi-linux-x64-musl": "0.43.0", - "@ast-grep/napi-win32-arm64-msvc": "0.43.0", - "@ast-grep/napi-win32-ia32-msvc": "0.43.0", - "@ast-grep/napi-win32-x64-msvc": "0.43.0" + "@ast-grep/napi-darwin-arm64": "0.44.1", + "@ast-grep/napi-darwin-x64": "0.44.1", + "@ast-grep/napi-linux-arm64-gnu": "0.44.1", + "@ast-grep/napi-linux-arm64-musl": "0.44.1", + "@ast-grep/napi-linux-x64-gnu": "0.44.1", + "@ast-grep/napi-linux-x64-musl": "0.44.1", + "@ast-grep/napi-win32-arm64-msvc": "0.44.1", + "@ast-grep/napi-win32-ia32-msvc": "0.44.1", + "@ast-grep/napi-win32-x64-msvc": "0.44.1" } }, "node_modules/@ast-grep/napi-darwin-arm64": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-arm64/-/napi-darwin-arm64-0.43.0.tgz", - "integrity": "sha512-JBWYRNJsQ/8yfcMDARFxcl1fVZ6+kv3sHB+ClKgSCe9DNoLvGQfqGr9UGRrNOModvXY6cX+c87iAPqPGftUUiw==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-arm64/-/napi-darwin-arm64-0.44.1.tgz", + "integrity": "sha512-JPcb1PvOkwKgccGbBgtoc4e6qfx/luvAtlc6SEtNPhvSwV8qagLb9MCSDcCiuqrqICx3qHj2CXouGbs66lCFvA==", "cpu": [ "arm64" ], @@ -81,9 +81,9 @@ } }, "node_modules/@ast-grep/napi-darwin-x64": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-x64/-/napi-darwin-x64-0.43.0.tgz", - "integrity": "sha512-oqE/bC/uAEctnAbzqobplidExsPym4CQq7JDVD9HpkHpDHzMzPBF69whv/Qu0ZwrkXr48GCMKO5G6p+OB5n+kQ==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-x64/-/napi-darwin-x64-0.44.1.tgz", + "integrity": "sha512-TZj9CnCoe30mj2Nri/mMDORgkp1nkeCiw03CIRPOaUqNOvCNyMWLQhxwGJcG1KpY9MuTZsbLcQ3jju0fXZJjwQ==", "cpu": [ "x64" ], @@ -97,9 +97,9 @@ } }, "node_modules/@ast-grep/napi-linux-arm64-gnu": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-gnu/-/napi-linux-arm64-gnu-0.43.0.tgz", - "integrity": "sha512-yJSRPxwwrvVW94J2rtaatcixSAGWcSaHNbAh6soXD6HXgq6I7uMc+cyMnJstFL789yd6Pu3QIhTlpD9VY0oYhw==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-gnu/-/napi-linux-arm64-gnu-0.44.1.tgz", + "integrity": "sha512-tw/7ruKolNcy97tDf/24Arb8wo/Ho2FRHk98lrmYQZZr18fFaiYjRXTCJTizOsdo6wL/asht4Rpdo/eRPuDhVw==", "cpu": [ "arm64" ], @@ -116,9 +116,9 @@ } }, "node_modules/@ast-grep/napi-linux-arm64-musl": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-musl/-/napi-linux-arm64-musl-0.43.0.tgz", - "integrity": "sha512-mknXLDsf66HvT/JEl18ZQSvR7/qWgWfqh3eHuVRqD2lE6cKBDXRnAzx9ZNZkUnL2Z5ph54Yk8dKVu09k45cegA==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-musl/-/napi-linux-arm64-musl-0.44.1.tgz", + "integrity": "sha512-IJlY4GnuDqABa40xMvJp62nEQWYmk6wXtZc2sbPiQoemxAPG6X2m+eWwYpE9heAQOe6iNkXsILNH8YZHRTYayA==", "cpu": [ "arm64" ], @@ -135,9 +135,9 @@ } }, "node_modules/@ast-grep/napi-linux-x64-gnu": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-gnu/-/napi-linux-x64-gnu-0.43.0.tgz", - "integrity": "sha512-KM6M5KKFsHG9Y7VKCKnMsWQQ1sYwj/SPdyr91SKp66AeFJ5xMtXb13WVQ3Joe9NEsi84dzzOBIJgMddz+UMvQw==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-gnu/-/napi-linux-x64-gnu-0.44.1.tgz", + "integrity": "sha512-6YyLrXn0RcKCS/FpqAFVWWtbGNFYia5tNI6mX1PqVQPCMT5EojHDXpW2/eo6uV/BnGCsVpTocAWSpac6tACa6g==", "cpu": [ "x64" ], @@ -154,9 +154,9 @@ } }, "node_modules/@ast-grep/napi-linux-x64-musl": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-musl/-/napi-linux-x64-musl-0.43.0.tgz", - "integrity": "sha512-NfjI74m7CEEOsLi7ZkYwcxY10CfKIHPHRrr9aqMulmnrC4FGvxQMk1qpDrTqkjmEd8LdZt3PsPrmBa8AZCErew==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-musl/-/napi-linux-x64-musl-0.44.1.tgz", + "integrity": "sha512-P8ze2Srap/4RsxbrzMy/NSvkRfLwNfvfoEbwBqS/q199t3wVkpS2gT13QkANrgU+RLUgAIawnFBpjbm5XinIuQ==", "cpu": [ "x64" ], @@ -173,9 +173,9 @@ } }, "node_modules/@ast-grep/napi-win32-arm64-msvc": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-arm64-msvc/-/napi-win32-arm64-msvc-0.43.0.tgz", - "integrity": "sha512-8qwXz3oogh834bXmNLtgsQYsuQi4ZDu4e1He/q+ENhVfjIr4IZRAUc1CoKZgWn+ZSE+B0NJJIxT8Vre2l0Q1Lg==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-arm64-msvc/-/napi-win32-arm64-msvc-0.44.1.tgz", + "integrity": "sha512-ty5fItgu6aTDJg1fVkWwx0qboB5pFsyjRHSgi5uqXFzLbno1vOKog1pMzu3xcKcLqMiNI0axML+HdFOqSFQz0w==", "cpu": [ "arm64" ], @@ -189,9 +189,9 @@ } }, "node_modules/@ast-grep/napi-win32-ia32-msvc": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-ia32-msvc/-/napi-win32-ia32-msvc-0.43.0.tgz", - "integrity": "sha512-wUI/B9QNPzi0K7LlyO7eWvwhqFChM6+O4r5dfBfnOlkP1s/AZ04FdYaC9KAZiODE4FCWbeHbfO03RYOdWeF6eg==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-ia32-msvc/-/napi-win32-ia32-msvc-0.44.1.tgz", + "integrity": "sha512-1wTv2MrQLygnaZhlgH8aYpOkHW051CwWWowTr2AFGkn+2PMfsTTG4SEWcZArOIKlAGdWAvvlut66tLyzKhAFyw==", "cpu": [ "ia32" ], @@ -205,9 +205,9 @@ } }, "node_modules/@ast-grep/napi-win32-x64-msvc": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-x64-msvc/-/napi-win32-x64-msvc-0.43.0.tgz", - "integrity": "sha512-GsGIwYiThKiK9XPGBEguZAs4aIBgv6+DgosBs6ve2a3L390AaLqitoc+bbRz4Eu51URlFKmNP7TtvnAJbMi8QQ==", + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-x64-msvc/-/napi-win32-x64-msvc-0.44.1.tgz", + "integrity": "sha512-U8CK5JaH8YUezTJnyk97I3C5Twh9gKDB/Q3KztQdOZGeoYqthnQzM4Ntvlz8aWaVSssn7OyGMr3O8ZoK0W8aJg==", "cpu": [ "x64" ], @@ -309,12 +309,12 @@ } }, "node_modules/@e18e/web-features-codemods": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@e18e/web-features-codemods/-/web-features-codemods-0.2.2.tgz", - "integrity": "sha512-5SoH8/AkAoKIYrLtn0FMjEQ0y70yZjAw0jPxg6VAIafMKG4B4Rh0yxojGQr0PUfNDTqWKgkToXT55LgTGYTb1Q==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@e18e/web-features-codemods/-/web-features-codemods-0.3.0.tgz", + "integrity": "sha512-NQ911GnWtomJTvgZAf4I/Scapczh3yNwqwsBXyF7et7IJCWKfCM3ke3c0bh+sLhahrWM/buSJTJ3iRKRKPNn6Q==", "license": "MIT", "dependencies": { - "@ast-grep/napi": "^0.43.0" + "@ast-grep/napi": "^0.44.0" } }, "node_modules/@emnapi/core": { @@ -2474,12 +2474,12 @@ "license": "MIT" }, "node_modules/module-replacements-codemods": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/module-replacements-codemods/-/module-replacements-codemods-2.0.1.tgz", - "integrity": "sha512-0F9WTGCH3W8e3pL6TCJggKtY5N2Pkmf93XEOEC7ndKd9yNK9mzPcVYft31RcRL3HuwMIOZUIIEkAsYUAenC67w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/module-replacements-codemods/-/module-replacements-codemods-2.0.2.tgz", + "integrity": "sha512-Eh4nSCaZk3vpT7OSLib1pKrMKiixfsXE7lxrMSDoGBCvb1UZ1dlmLcmPC8olKEGvQUNaFQ8XTmxHTYXcmZBrlg==", "license": "MIT", "dependencies": { - "@ast-grep/napi": "^0.43.0" + "@ast-grep/napi": "^0.44.0" } }, "node_modules/mri": { diff --git a/package.json b/package.json index 2127117..0e56348 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "homepage": "https://github.com/e18e/cli#readme", "dependencies": { "@clack/prompts": "^1.7.0", - "@e18e/web-features-codemods": "^0.2.1", + "@e18e/web-features-codemods": "^0.3.0", "@publint/pack": "^0.1.5", "core-js-compat": "^3.48.0", "enginematch": "^0.2.0", @@ -56,7 +56,7 @@ "gunshi": "^0.35.1", "lockparse": "^0.5.2", "module-replacements": "^3.0.0", - "module-replacements-codemods": "^2.0.0", + "module-replacements-codemods": "^2.0.1", "obug": "^2.1.3", "package-manager-detector": "^1.7.0", "publint": "^0.3.21", diff --git a/src/analyze/web-features-codemods.ts b/src/analyze/web-features-codemods.ts index 39a1be3..4c7dc92 100644 --- a/src/analyze/web-features-codemods.ts +++ b/src/analyze/web-features-codemods.ts @@ -45,25 +45,22 @@ export async function runWebFeaturesCodemodsAnalysis( continue; } - const matches: string[] = []; for (const [name, codemod] of webFeatureCodemods) { try { - if (codemod.test({source})) { - matches.push(name); + const testResult = codemod.test({source}); + if (testResult.hasMatch) { + messages.push({ + severity: 'suggestion', + score: 0, + file: filePath, + message: `Consider using the ${name} feature here.`, + range: testResult.range + }); } } catch { continue; } } - - if (matches.length > 0) { - messages.push({ - severity: 'suggestion', - score: 0, - file: filePath, - message: `Can use newer web features: ${matches.join(', ')}.` - }); - } } return {messages}; diff --git a/src/commands/analyze.ts b/src/commands/analyze.ts index cdcf8ad..28b6896 100644 --- a/src/commands/analyze.ts +++ b/src/commands/analyze.ts @@ -211,15 +211,16 @@ export async function run(ctx: CommandContext) { const formatBulletMessage = ( text: string, - label: (typeof labels)[keyof typeof labels] + label: (typeof labels)[keyof typeof labels], + location = '' ) => { const severity = styleText(label.color, label.text.padEnd(labelWidth)); - const indent = ' '.repeat(gutter); + const locationCol = location ? `${styleText('dim', location)} ` : ''; + const prefix = ` ${locationCol}${severity} `; + const indent = ' '.repeat(gutter + (location ? location.length + 2 : 0)); return wrapAnsi(text, maxContentWidth) .split('\n') - .map((line, i) => - i === 0 ? ` ${severity} ${line}` : `${indent}${line}` - ) + .map((line, i) => (i === 0 ? `${prefix}${line}` : `${indent}${line}`)) .join('\n'); }; @@ -256,8 +257,11 @@ export async function run(ctx: CommandContext) { spacing: 0 }); for (const msg of group) { + const location = msg.range + ? `${msg.range.start.line + 1}:${msg.range.start.column + 1}` + : ''; prompts.log.message( - formatBulletMessage(msg.message, labels[msg.severity]), + formatBulletMessage(msg.message, labels[msg.severity], location), {spacing: 0} ); } diff --git a/src/test/analyze/__snapshots__/web-features-codemods.test.ts.snap b/src/test/analyze/__snapshots__/web-features-codemods.test.ts.snap index e11d21c..c9dd8b4 100644 --- a/src/test/analyze/__snapshots__/web-features-codemods.test.ts.snap +++ b/src/test/analyze/__snapshots__/web-features-codemods.test.ts.snap @@ -4,7 +4,33 @@ exports[`runWebFeaturesCodemodsAnalysis > handles a file with multiple matches 1 [ { "file": "index.js", - "message": "Can use newer web features: arrayAt, exponentiation.", + "message": "Consider using the arrayAt feature here.", + "range": { + "end": { + "column": 36, + "line": 0, + }, + "start": { + "column": 13, + "line": 0, + }, + }, + "score": 0, + "severity": "suggestion", + }, + { + "file": "index.js", + "message": "Consider using the exponentiation feature here.", + "range": { + "end": { + "column": 34, + "line": 1, + }, + "start": { + "column": 16, + "line": 1, + }, + }, "score": 0, "severity": "suggestion", }, @@ -17,7 +43,17 @@ exports[`runWebFeaturesCodemodsAnalysis > handles a file with one match 1`] = ` [ { "file": "index.js", - "message": "Can use newer web features: arrayAt.", + "message": "Consider using the arrayAt feature here.", + "range": { + "end": { + "column": 36, + "line": 0, + }, + "start": { + "column": 13, + "line": 0, + }, + }, "score": 0, "severity": "suggestion", }, @@ -28,7 +64,17 @@ exports[`runWebFeaturesCodemodsAnalysis > handles multiple occurrences of the sa [ { "file": "index.js", - "message": "Can use newer web features: arrayAt.", + "message": "Consider using the arrayAt feature here.", + "range": { + "end": { + "column": 40, + "line": 0, + }, + "start": { + "column": 17, + "line": 0, + }, + }, "score": 0, "severity": "suggestion", }, @@ -41,7 +87,17 @@ exports[`runWebFeaturesCodemodsAnalysis > respects the src option 1`] = ` [ { "file": "src/index.js", - "message": "Can use newer web features: arrayAt.", + "message": "Consider using the arrayAt feature here.", + "range": { + "end": { + "column": 36, + "line": 0, + }, + "start": { + "column": 13, + "line": 0, + }, + }, "score": 0, "severity": "suggestion", }, diff --git a/src/types.ts b/src/types.ts index 7d0fbe9..5492f45 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,6 +29,16 @@ export interface Stats { extraStats?: Stat[]; } +export interface Position { + line: number; + column: number; +} + +export interface Range { + start: Position; + end: Position; +} + export interface Message { severity: 'error' | 'warning' | 'suggestion'; score: number; @@ -37,6 +47,7 @@ export interface Message { file?: string; /** Command that can fix this message (e.g. 'migrate'). */ fixableBy?: string; + range?: Range; } export interface PackageJsonLike {