Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
node_modules/
# Build output
out/
coverage/
dist/

# Dependencies
node_modules/

# Test runner and coverage
.vscode-test/
coverage/

# Packaged extension
*.vsix

# macOS
.DS_Store
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Pathverse Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Dart References

Show reference counts for Dart symbols directly in the editor using CodeLens, with optional warnings for unused functions and methods.

## Requirements

- VS Code 1.100.0+
- A Dart language server (install the official Dart/Flutter VS Code extension)

## How It Works

- Uses document symbols to find Dart functions, classes, methods, constructors, variables, and enums.
- For each symbol, queries references and shows a CodeLens label.
- If a function or method has zero references, a warning diagnostic is added.

## Configuration

Settings are under `dartReferences`:

- `dartReferences.enable`: Enable/disable CodeLens and diagnostics (default: `true`).
- `dartReferences.referencesLabel`: Label used for non-zero reference counts (default: `references`).
- `dartReferences.zeroReferencesLabel`: Label used for zero references (default: `No references`).
- `dartReferences.ignoredMethods`: Method names to ignore (default: Flutter lifecycle and common overrides).

## Notes

- Reference counts rely on the Dart language server; results may be delayed in large files.
- Ignored methods are matched by name only (not by class or signature).
Binary file added dart-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 23 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"name": "dart-references",
"displayName": "dart-references",
"displayName": "Dart References",
"description": "Displays the number of references for Dart and Flutter in VSCode",
"author": "Pathverse Inc.",
"license": "MIT",
"version": "0.1.0",
"icon": "dart-logo.png",
"publisher": "pathverse",
"repository": {
"type": "git",
Expand All @@ -17,7 +20,6 @@
"activationEvents": [
"onLanguage:dart"
],
"icon": "icon.png",
"main": "./out/extension.js",
"contributes": {
"configuration": {
Expand All @@ -38,6 +40,25 @@
"default": "No references",
"description": "Label displayed when there are no references found for a Dart functions, classes, methods, constructors, variables, and enums."
},
"dartReferences.ignoredMethods": {
"type": "array",
"default": [
"build",
"initState",
"dispose",
"didChangeDependencies",
"didUpdateWidget",
"reassemble",
"deactivate",
"setState",
"createState",
"toString",
"hashCode",
"operator==",
"noSuchMethod"
],
"description": "List of method names to ignore when displaying reference counts (e.g., Flutter lifecycle methods and common overrides)."
},
"dartReferences.cacheMaxEntries": {
"type": "number",
"default": 2000,
Expand Down
28 changes: 28 additions & 0 deletions src/core/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export interface DartReferencesSettings {
enabled: boolean;
referencesLabel: string;
zeroReferencesLabel: string;
/** Method names that never get a lens or unused diagnostic (e.g. Flutter lifecycle overrides). */
ignoredMethods: ReadonlySet<string>;
/** Maximum cached symbols (LRU-evicted); 0 = unlimited. */
cacheMaxEntries: number;
/** Above this many locations only the count is cached; 0 = unlimited. */
Expand All @@ -14,6 +16,7 @@ export interface RawSettings {
enable?: unknown;
referencesLabel?: unknown;
zeroReferencesLabel?: unknown;
ignoredMethods?: unknown;
cacheMaxEntries?: unknown;
maxCachedLocations?: unknown;
cacheTtlSeconds?: unknown;
Expand All @@ -27,6 +30,30 @@ function labelOrDefault(raw: unknown, fallback: string): string {
return trimmed.length > 0 ? trimmed : fallback;
}

// Mirrors the dartReferences.ignoredMethods default in package.json.
const defaultIgnoredMethods: readonly string[] = [
'build',
'initState',
'dispose',
'didChangeDependencies',
'didUpdateWidget',
'reassemble',
'deactivate',
'setState',
'createState',
'toString',
'hashCode',
'operator==',
'noSuchMethod',
];

function ignoredMethodsOrDefault(raw: unknown): ReadonlySet<string> {
if (!Array.isArray(raw)) {
return new Set(defaultIgnoredMethods);
}
return new Set(raw.filter((name): name is string => typeof name === 'string'));
}

function limitOrDefault(raw: unknown, fallback: number): number {
if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) {
return fallback;
Expand All @@ -39,6 +66,7 @@ export function normalizeSettings(raw: RawSettings): DartReferencesSettings {
enabled: raw.enable !== false,
referencesLabel: labelOrDefault(raw.referencesLabel, 'references'),
zeroReferencesLabel: labelOrDefault(raw.zeroReferencesLabel, 'No references'),
ignoredMethods: ignoredMethodsOrDefault(raw.ignoredMethods),
cacheMaxEntries: limitOrDefault(raw.cacheMaxEntries, 2000),
maxCachedLocations: limitOrDefault(raw.maxCachedLocations, 1000),
cacheTtlMs: limitOrDefault(raw.cacheTtlSeconds, 60) * 1000,
Expand Down
1 change: 1 addition & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function activate(context: vscode.ExtensionContext) {
enable: config.get('enable'),
referencesLabel: config.get('referencesLabel'),
zeroReferencesLabel: config.get('zeroReferencesLabel'),
ignoredMethods: config.get('ignoredMethods'),
cacheMaxEntries: config.get('cacheMaxEntries'),
maxCachedLocations: config.get('maxCachedLocations'),
cacheTtlSeconds: config.get('cacheTtlSeconds'),
Expand Down
8 changes: 6 additions & 2 deletions src/referenceLensProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { CachedReferences, ReferenceCache } from './core/referenceCache';
import { DocumentDiagnosticsStore } from './core/diagnosticsStore';
import { collectRelevantSymbols } from './core/symbols';
import { collectRelevantSymbols, symbolKinds } from './core/symbols';
import { countNonDeclarationReferences, LocationLike } from './core/referenceCount';
import { DartReferencesSettings, formatLensTitle } from './core/settings';
import { shouldFlagUnused, unusedSymbolMessage } from './core/unused';
Expand Down Expand Up @@ -56,7 +56,8 @@ export class ReferenceCountCodeLensProvider implements vscode.CodeLensProvider {
}

async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
if (!this.deps.getSettings().enabled) {
const settings = this.deps.getSettings();
if (!settings.enabled) {
this.diagnostics.clearDocument(document.uri.toString());
this.deps.setDiagnostics(document.uri, []);
return [];
Expand All @@ -72,6 +73,9 @@ export class ReferenceCountCodeLensProvider implements vscode.CodeLensProvider {

const lenses: vscode.CodeLens[] = [];
for (const symbol of collectRelevantSymbols(symbols)) {
if (symbol.kind === symbolKinds.method && settings.ignoredMethods.has(symbol.name)) {
continue;
}
const lineStart = new vscode.Position(symbol.selectionRange.start.line, 0);
const lens = new vscode.CodeLens(new vscode.Range(lineStart, lineStart));
this.lensMetadata.set(lens, {
Expand Down
10 changes: 10 additions & 0 deletions src/test/unit/referenceLensProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ describe('ReferenceCountCodeLensProvider', () => {
);
});

it('skips methods listed in ignoredMethods but not functions sharing the name', async () => {
symbolsResult = [
fakeSymbol('build', SymbolKind.Method, 2),
fakeSymbol('build', SymbolKind.Function, 5),
fakeSymbol('helper', SymbolKind.Method, 8),
];
const lenses = await provider.provideCodeLenses(document(), token());
assert.deepStrictEqual(lenses.map(l => l.range.start.line), [5, 8]);
});

it('returns no lenses and clears diagnostics when disabled', async () => {
settings = normalizeSettings({ enable: false });
const lenses = await provider.provideCodeLenses(document(), token());
Expand Down
20 changes: 20 additions & 0 deletions src/test/unit/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ describe('normalizeSettings', () => {
enabled: true,
referencesLabel: 'references',
zeroReferencesLabel: 'No references',
ignoredMethods: new Set([
'build',
'initState',
'dispose',
'didChangeDependencies',
'didUpdateWidget',
'reassemble',
'deactivate',
'setState',
'createState',
'toString',
'hashCode',
'operator==',
'noSuchMethod',
]),
cacheMaxEntries: 2000,
maxCachedLocations: 1000,
cacheTtlMs: 60_000,
Expand Down Expand Up @@ -56,6 +71,11 @@ describe('normalizeSettings', () => {
assert.strictEqual(normalizeSettings({ cacheMaxEntries: 250.7 }).cacheMaxEntries, 250);
});

it('uses the configured ignoredMethods list, dropping non-string entries', () => {
const settings = normalizeSettings({ ignoredMethods: ['mine', 42] });
assert.deepStrictEqual([...settings.ignoredMethods], ['mine']);
});

it('falls back to defaults for negative or non-numeric limits', () => {
const settings = normalizeSettings({ cacheMaxEntries: -5, maxCachedLocations: 'lots', cacheTtlSeconds: NaN });
assert.strictEqual(settings.cacheMaxEntries, 2000);
Expand Down
Loading