a2ui-project / a2ui-project/a2ui
TypeScript code contains hundreds of references to `any` type, which disables type analysis
- Lingua principale
- TypeScript
- Stelle
- 16.4k
- Fork
- 1.3k
- Merge medio
- 2g 13h
- PR unite (30g)
- 134
Descrizione
If we enabled `@typescript-eslint/no-explicit-any: "error"` and ran `tsc --noImplicitAny`, **381 errors** would surface in production code that the currently are not flagged.
Each of these place where a type was either explicitly declared `any` (effectively turing off the type checker) or never specified at all (compiler couldn't infer). Each one is a hole through which a wrong type can flow into otherwise type-checked code.
Regardless of how many references of `any` turn out to actually be problematic, I suggest we turn on `no-explicit-any`, at least in non-test code. The whole purpose of the `any` super type, as far I am aware, is to allow gradual migration of JavaScript to TypeScript. This doesn't apply to this repo since the relevant code was written in TS to begin with. Even in places were the shape of an object cannot be known ahead of time, `unknown` can be used. TS's type system is very expressive--there's usually a way to keep things typed without great upfront cost even in scenarios where it is tempting to reach for `any` (e.g. discriminating union types, intersection types, mapped types, conditional types, template literal types, `infer`, etc.).
I suggest we turn on the rule sooner rather than later. The longer we put it off, the more expensive the issue becomes. I can personally say I've seen agents spending several inferences troubleshooting a bug that would have been immediately flagged with typechecking in my very brief time with this repo.
Use of `any` can also hide API design smells that. For example, being forced to write a very complicated or brittle type ([example](https://github.com/google/A2UI/blob/1e82275ec0644aea41471f53bbfd79587c9e6b44/renderers/angular/src/v0_9/core/types.ts#L70-L76)) often signals that an upstream type is loosely specified (e.g. a single blobby type with a bunch of optional fields was used where a discriminating union would more precisely capture that an object will match one of _N_ known shapes at runtime).
## Numbers
The following was gathered via an agent, but the full methodology is included afterwards to facilitate verification.
| Category | Errors |
|---|---:|
| **Published library code** *(`renderers/web_core`, `renderers/{angular,lit,react}`, `renderers/markdown/markdown-it`)* | **146** |
| **All other non-test code** *(samples, tools, spec eval)* | **235** |
| **Total** | **381** |
Test/spec code was excluded, though of course this could mask bugs as well.
Of the 381: **2 are implicit-any** (compiler couldn't infer) and **379 are explicit `any`** (written deliberately in source). Implicit-any is more or less fine here since we have `strict: true` in all our tsconfigs.
### Library breakdown
| Package | Errors |
|---|---:|
| `renderers/web_core` | 99 |
| `renderers/angular` | 29 |
| `renderers/lit` | 12 |
| `renderers/react` | 4 |
| `renderers/markdown/markdown-it` | 2 |
## Suggested fix
Enable `@typescript-eslint/no-explicit-any: "error"` in every workspace and gate CI on it. Fix or add `// eslint-disable-next-line` each existing site as a tracked decision rather than silent permission. Focus on library code first; samples/demos/tests can follow.
`web_core` has 99 explicit uses of `any`. [The eslint config defends this as necessary for generic library code](https://github.com/google/A2UI/blob/c55a37dc783146a607003ff3a3ecda92fbc36ac1/renderers/web_core/eslint.config.js#L33-L40). However--while I have not personally reviewed all uses--I have to imagine they are replaceable with `unknown` + narrowing or proper generic constraints. Adding [Type guards](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) using `any` for the input parameter and removing `as any` casts that monkey-patch missing properties are probably the highest-leverage fixes we can do.
## Reproduction
Repo at `main` commit `c55a37dc`, all workspaces `npm install`ed first (otherwise `tsc` reports phantom errors from unresolved imports).
**Implicit any per workspace** (production code only):
```bash
cd
tsc --noEmit --noImplicitAny -p tsconfig.json 2>&1 \
| grep -vE '\.(test|spec)\.(ts|tsx)' \
| grep -cE 'error TS(7005|7006|7008|7009|7010|7011|7015|7017|7018|7019|7022|7023|7024|7031|7032|7033|7034)'
```
The TS70xx codes are TypeScript's "implicitly has an 'any' type" diagnostic family.
**Explicit any.** The following code performs a repo-wide AST walk that counts every `any` keyword in production `.ts`/`.tsx` files. Save as `count-any.js` and run from the repo root:
```js
// count-any.js
const ts = require('typescript');
const fs = require('fs');
const path = require('path');
const SKIP = new Set([
'node_modules', 'dist', 'build', 'out', 'coverage',
'.angular', '.next', '.wireit', '.git', 'worktrees',
]);
const TEST = /\.(test|spec)\.tsx?$|\/tests?\/|\/__tests__\//;
function* walk(dir) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
if (SKIP.has(e.name) || e.name.startsWith('.')) continue;
const p = path.join(dir, e.name);
if (e.isDirectory()) {
yield* walk(p);
} else if (
/\.tsx?$/.test(e.name) &&
!e.name.endsWith('.d.ts') &&
!TEST.test(p)
) {
yield p;
}
}
}
let total = 0;
for (const file of walk(process.cwd())) {
const sf = ts.createSourceFile(
file,
fs.readFileSync(file, 'utf8'),
ts.ScriptTarget.Latest,
/* setParentNodes */ false,
file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
);
(function visit(node) {
if (node.kind === ts.SyntaxKind.AnyKeyword) total++;
ts.forEachChild(node, visit);
})(sf);
}
console.log(total);
```
```bash
node count-any.js
```
Equivalent to running ESLint with `@typescript-eslint/no-explicit-any: "error"` and counting violations, but this script works without needing per-workspace ESLint configuration.
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.