script-utils/linters/over-exposure
Over-exposure analyzer.
Finds declarations exposed more broadly than their references require, so the exposure can be tightened:
exported symbols referenced only within their own file — theexportcan be dropped. -publicclass members referenced only inside their own class — they can beprivate. -publicclass members referenced only inside their own class and its subclasses — they can beprotected. -protectedclass members referenced only inside their own class — they can beprivate.
The analysis is whole-program and type-aware: it uses the TypeScript language service’s find-all-references to locate every use of each declaration, then classifies the tightest exposure that still covers all references. Because findReferences does not link a string-literal argument back to a member declaration when the parameter is a conditional/mapped key type (e.g. bind(component, 'someSetting') where the key is typed ConditionalKeys<Settings, …>), member analysis additionally treats a string literal whose text equals the member name as a reference — but only when its contextual type is a string-literal type (or union of them) all of whose members are keys of the declaring class, so incidental same-named strings (console.log('name')) are not miscounted. Members invoked by the framework via registration rather than by visible references (Obsidian lifecycle hooks, settings-tab display, etc.) are excluded via LIFECYCLE_ALLOWLIST, as are override and static members whose exposure is constrained by a base declaration. ECMAScript hard-private members (#name) are also excluded — they are already maximally private and cannot carry a visibility modifier. A declaration carrying a TSDoc (/** … *\/) documentation comment is likewise excluded — whether a top-level export or a class member, documenting it is a deliberate signal that it is part of the intended public API, regardless of where it currently happens to be referenced.
A member referenced only from test files is reported with OverExposureFinding.isForcedByTestOnly set, surfacing members widened purely for testability — the canonical case for extracting logic into an independently testable component instead.
Beyond reporting, passing shouldFix to findOverExposure / analyzeOverExposure rewrites the source in place: dropping the export keyword, or replacing/inserting a private or protected modifier. Each finding then carries OverExposureFinding.wasFixed / OverExposureFinding.skipReason. Changes that cannot be safely automated — those forced only by test references, decorated members, or an export shared with a still-exported sibling — are reported as skipped and left untouched.
Additionally passing shouldForce (only meaningful together with shouldFix) tightens the declarations held wide purely by test references too — the production half of the canonical test-only migration: the member becomes private / the export is dropped, leaving only the test to switch to a cast-based access. Decorated members and exports shared with a still-exported sibling stay skipped even under shouldForce, because no edit can be applied safely.
Interfaces
Section titled “Interfaces”| Interface | Description |
|---|---|
| AnalyzeOverExposureParams | Parameters for analyzeOverExposure. |
| CreateLanguageServiceHostParams | Parameters for createLanguageServiceHost. |
| CreateProjectLanguageServiceParams | Parameters for createProjectLanguageService. |
| FindOverExposureParams | Parameters for findOverExposure. |
| FormatOverExposureFindingsOptions | Options for formatOverExposureFindings. |
| OverExposureFileSystem | The subset of file-system operations the language-service host needs. Injecting this (rather than always using typescript’s sys) lets tests drive the analyzer against in-memory sources with no real file-system access. |
| OverExposureFinding | A single over-exposed declaration. |
| OverExposureProgress | Progress reported while analyzing a project, one event per analyzed source file. |
Functions
Section titled “Functions”| Function | Description |
|---|---|
| analyzeOverExposure | Analyzes a project (already loaded into a language service) for over-exposed declarations, and — when AnalyzeOverExposureParams.shouldFix is set — tightens every fixable finding in place via AnalyzeOverExposureParams.writeFile. Findings that cannot be safely automated (exposed only for tests, decorated, or sharing an export keyword with a still-exported sibling) carry a OverExposureFinding.skipReason and are left untouched — unless AnalyzeOverExposureParams.shouldForce is set, which additionally tightens the test-only findings (decorated and shared-export ones still stay skipped). |
| createLanguageServiceHost | Builds a LanguageServiceHost backed by an explicit file list and file system. |
| createProjectLanguageService | Builds a language service over every file in a project’s tsconfig.json. |
| findOverExposure | Finds over-exposed declarations in a project on disk. When FindOverExposureParams.shouldFix is set, each fixable finding is also tightened in place (rewriting the affected source files via typescript’s sys.writeFile). |
| formatOverExposureFindings | Formats over-exposure findings as a human-readable report, using the grouped layout of ESLint’s stylish formatter. Findings are grouped by file and sorted by line: each file is rendered as a path header line, followed by one indented line:column change -- reason row per finding (the line:column is right-padded so the rows align within a file). Terminals such as VS Code render each indented line:column as a clickable link — resolved against the file header above it — that jumps straight to the declaration. File groups are separated by a blank line. In a fix run (findings carrying OverExposureFinding.wasFixed / OverExposureFinding.skipReason) each change row is suffixed with [fixed] or [skipped: …], and the trailing summary appends the fixed/skipped counts. |
| Type | Description |
|---|---|
| CurrentExposure | The exposure level a declaration currently has. |
| OverExposureSkipReason | Why a fix run left a finding untouched: - decorated — the member carries a decorator, so the modifier insertion point is ambiguous. - shared-export — the export keyword is shared with a still-exported sibling declarator. - test-only — the declaration is exposed purely for tests, so tightening would break the test. |
| SuggestedExposure | The (tighter) exposure level a declaration could be reduced to. |
Variables
Section titled “Variables”| Variable | Description |
|---|---|
| LIFECYCLE_ALLOWLIST | Lifecycle and framework members invoked by Obsidian or a base class via registration rather than by references the language service can see. Tightening these would break the framework contract, so they are never reported. |