| 1 | import assert from "node:assert/strict"; |
| 2 | import { readdirSync, readFileSync } from "node:fs"; |
| 3 | import { dirname, join, relative } from "node:path"; |
| 4 | import { fileURLToPath } from "node:url"; |
| 5 | import ts from "typescript"; |
| 6 | |
| 7 | // Prevent newly added inline error labels from bypassing the shared presenter. |
| 8 | // Raw diagnostics in explicit detail/code views remain verbatim by design. |
| 9 | const root = join(dirname(fileURLToPath(import.meta.url)), ".."); |
| 10 | function files(directory: string): string[] { |
| 11 | return readdirSync(directory, { withFileTypes: true }).flatMap(entry => |
| 12 | entry.name === "__tests__" ? [] : entry.isDirectory() ? files(join(directory, entry.name)) : [join(directory, entry.name)]); |
| 13 | } |
| 14 | const violations: string[] = []; |
| 15 | for (const file of files(root).filter(name => name.endsWith(".tsx") && !name.endsWith(".test.tsx"))) { |
| 16 | const source = ts.createSourceFile(file, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); |
| 17 | function visit(node: ts.Node) { |
| 18 | if (ts.isJsxExpression(node) && node.expression && ts.isJsxElement(node.parent)) { |
| 19 | const expression = node.expression.getText(source); |
| 20 | const tag = node.parent.openingElement.tagName.getText(source); |
| 21 | if (/^(?:[a-zA-Z_$][\w$]*\??\.)*(?:\w*(?:Err|Error)|err|error)$/.test(expression) |
| 22 | && /^[a-z]/.test(tag) && !["pre", "code"].includes(tag)) { |
| 23 | violations.push(`${relative(root, file)}:${source.getLineAndCharacterOfPosition(node.pos).line + 1}: ${expression}`); |
| 24 | } |
| 25 | } |
| 26 | ts.forEachChild(node, visit); |
| 27 | } |
| 28 | visit(source); |
| 29 | } |
| 30 | assert.deepEqual(violations, [], "User-visible errors must go through ErrorMessage; raw details belong in explicit diagnostic views"); |
| 31 | console.log("PASS error display contract: inline error surfaces use the shared presenter"); |
| 32 |