feat: configure eslint with unused import removal (#564)

* feat: configure eslint with unused import removal

Add ESLint v9 (flat config) for code quality:
- eslint-plugin-unused-imports for auto-removing dead imports
- @typescript-eslint for TypeScript-aware linting
- eslint-plugin-react-hooks for React hooks rules
- eslint-config-prettier to avoid formatting conflicts
- lint-staged runs eslint --fix before prettier on .ts/.tsx
- CI lint job added to ci-quality.yml

* refactor: remove unused imports via eslint --fix

Auto-fixed by eslint-plugin-unused-imports. No logic changes.

* chore: add eslint fix commit to .git-blame-ignore-revs
This commit is contained in:
Gergő Magyar 2026-03-28 15:28:09 +00:00 committed by GitHub
parent bf09eab95b
commit acf6fbdd39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 2162 additions and 60 deletions

View file

@ -1,2 +1,5 @@
# Prettier initial formatting (2026-03-28)
afcc3d1523f99c77ff67c4fd1af12334660113f6
# ESLint unused import removal (2026-03-28)
1491826bc8da5436d3b1eb092d9274f2c9f028a7

View file

@ -17,6 +17,19 @@ jobs:
- run: npm ci
- run: npx prettier --check .
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: npm ci
- run: npx eslint .
typecheck:
runs-on: ubuntu-latest
timeout-minutes: 10

83
eslint.config.mjs Normal file
View file

@ -0,0 +1,83 @@
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import unusedImports from 'eslint-plugin-unused-imports';
import reactHooks from 'eslint-plugin-react-hooks';
import prettierConfig from 'eslint-config-prettier';
export default [
// Global ignores
{
ignores: [
'**/dist/**',
'**/node_modules/**',
'**/coverage/**',
'gitnexus/vendor/**',
'gitnexus-web/src/vendor/**',
'gitnexus/test/fixtures/**',
'gitnexus-web/playwright-report/**',
'gitnexus-web/test-results/**',
'**/*.d.ts',
'.claude/**',
'.history/**',
],
},
// Base TypeScript config for all packages
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
'unused-imports': unusedImports,
},
rules: {
// Unused imports — auto-fixable
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'warn',
{ vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
],
// TypeScript quality
'@typescript-eslint/no-unused-vars': 'off', // handled by unused-imports plugin
'no-unused-vars': 'off', // handled by unused-imports plugin
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
// General quality
'no-debugger': 'error',
'prefer-const': 'error',
'no-var': 'error',
eqeqeq: ['error', 'always', { null: 'ignore' }],
},
},
// CLI package — allow console.log (it's a CLI tool)
{
files: ['gitnexus/src/cli/**/*.ts', 'gitnexus/src/server/**/*.ts'],
rules: {
'no-console': 'off',
},
},
// React-specific rules for gitnexus-web
{
files: ['gitnexus-web/src/**/*.{ts,tsx}'],
plugins: {
'react-hooks': reactHooks,
},
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
},
// Disable formatting rules (prettier handles those)
prettierConfig,
];

View file

@ -1,4 +1,4 @@
import { test, expect, type TestInfo } from '@playwright/test';
import { test, expect } from '@playwright/test';
/**
* Debug harnesses for investigating specific UI issues.

View file

@ -19,13 +19,7 @@ import {
Type,
} from '@/lib/lucide-icons';
import { useAppState } from '../hooks/useAppState';
import {
FILTERABLE_LABELS,
NODE_COLORS,
ALL_EDGE_TYPES,
EDGE_INFO,
type EdgeType,
} from '../lib/constants';
import { FILTERABLE_LABELS, NODE_COLORS, ALL_EDGE_TYPES, EDGE_INFO } from '../lib/constants';
import type { GraphNode, NodeLabel } from 'gitnexus-shared';
// Tree node structure

View file

@ -2,7 +2,6 @@ import { Suspense, useEffect, useRef, useState, lazy } from 'react';
import mermaid from 'mermaid';
import DOMPurify from 'dompurify';
import { AlertTriangle, Maximize2 } from '@/lib/lucide-icons';
import type { ProcessData } from '../lib/mermaid-generator';
const ProcessFlowModal = lazy(() =>
import('./ProcessFlowModal').then((m) => ({ default: m.ProcessFlowModal })),

View file

@ -5,7 +5,7 @@
*/
import { useEffect, useRef, useCallback, useState } from 'react';
import { X, GitBranch, Copy, Focus, Layers, ZoomIn, ZoomOut } from 'lucide-react';
import { Copy, Focus, ZoomIn, ZoomOut } from 'lucide-react';
import mermaid from 'mermaid';
import DOMPurify from 'dompurify';
import { ProcessData, generateProcessMermaid } from '../lib/mermaid-generator';

View file

@ -8,12 +8,11 @@ import {
useMemo,
ReactNode,
} from 'react';
import type { GraphNode, GraphRelationship, NodeLabel, PipelineProgress } from 'gitnexus-shared';
import type { GraphNode, NodeLabel, PipelineProgress } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../core/graph/types';
import { createKnowledgeGraph } from '../core/graph/graph';
import type {
LLMSettings,
ProviderConfig,
AgentStreamChunk,
ChatMessage,
ToolCallInfo,
@ -23,7 +22,6 @@ import { loadSettings, getActiveProviderConfig, saveSettings } from '../core/llm
import type { AgentMessage } from '../core/llm/agent';
import { type EdgeType } from '../lib/constants';
import {
fetchRepos,
connectToServer,
runQuery as backendRunQuery,
search as backendSearch,

View file

@ -12,7 +12,6 @@ import path from 'path';
import { PipelineResult } from '../types/pipeline.js';
import { CommunityNode, CommunityMembership } from '../core/ingestion/community-processor.js';
import { ProcessNode } from '../core/ingestion/process-processor.js';
import type { GraphNode } from 'gitnexus-shared';
import { KnowledgeGraph } from '../core/graph/types.js';
// ============================================================================

View file

@ -150,7 +150,7 @@ export const initEmbedder = async (
// if we attempt CUDA without the required shared libraries
const isWindows = process.platform === 'win32';
const gpuDevice = isWindows ? 'dml' : isCudaAvailable() ? 'cuda' : 'cpu';
let requestedDevice =
const requestedDevice =
forceDevice || (finalConfig.device === 'auto' ? gpuDevice : finalConfig.device);
initPromise = (async () => {

View file

@ -16,7 +16,7 @@ import {
embeddingToArray,
isEmbedderReady,
} from './embedder.js';
import { generateBatchEmbeddingTexts, generateEmbeddingText } from './text-generator.js';
import { generateBatchEmbeddingTexts } from './text-generator.js';
import {
type EmbeddingProgress,
type EmbeddingConfig,

View file

@ -20,7 +20,6 @@ import {
inferCallForm,
extractReceiverName,
extractReceiverNode,
CALL_EXPRESSION_TYPES,
extractMixedChain,
type MixedChainStep,
} from './utils/call-analysis.js';

View file

@ -13,7 +13,6 @@ import { getTreeSitterBufferSize } from './constants.js';
import { loadImportConfigs } from './language-config.js';
import { buildSuffixIndex } from './import-resolvers/utils.js';
import type { ResolutionContext, ModuleAliasMap } from './resolution-context.js';
import type { SuffixIndex } from './import-resolvers/utils.js';
import type {
ImportResult,
ResolveCtx,

View file

@ -3,8 +3,6 @@
* Extracted from import-processor.ts to reduce file size.
*/
import type { SyntaxNode } from '../utils/ast-helpers.js';
/** All file extensions to try during resolution */
export const EXTENSIONS = [
'',

View file

@ -8,7 +8,7 @@
import path from 'node:path';
import { generateId } from '../../lib/utils.js';
import type { GraphNode, GraphRelationship } from 'gitnexus-shared';
import type { GraphNode } from 'gitnexus-shared';
import { KnowledgeGraph } from '../graph/types.js';
const HEADING_RE = /^(#{1,6})\s+(.+)$/;

View file

@ -19,7 +19,6 @@
* Cypher: MATCH (c:Class)-[r:CodeRelation {type: 'OVERRIDES'}]->(m:Method)
*/
import type { GraphRelationship } from 'gitnexus-shared';
import { KnowledgeGraph } from '../graph/types.js';
import { generateId } from '../../lib/utils.js';
import { SupportedLanguages } from 'gitnexus-shared';

View file

@ -1,4 +1,4 @@
import type { GraphNode, GraphRelationship, NodeLabel } from 'gitnexus-shared';
import type { GraphNode, GraphRelationship } from 'gitnexus-shared';
import { KnowledgeGraph } from '../graph/types.js';
import Parser from 'tree-sitter';
import { loadParser, loadLanguage, isLanguageAvailable } from '../tree-sitter/parser-loader.js';

View file

@ -54,7 +54,7 @@ import { PipelineResult } from '../../types/pipeline.js';
import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
import { SupportedLanguages } from 'gitnexus-shared';
import { providers, getProvider, getProviderForFile } from './languages/index.js';
import { providers, getProviderForFile } from './languages/index.js';
import { createWorkerPool, WorkerPool } from './workers/worker-pool.js';
import fs from 'node:fs';
import path from 'node:path';
@ -376,7 +376,7 @@ async function runCrossFileBindingPropagation(
let crossFileResolved = 0;
const crossFileStart = Date.now();
let astCache = createASTCache(AST_CACHE_CAP);
const astCache = createASTCache(AST_CACHE_CAP);
for (const level of levels) {
const levelCandidates: {

View file

@ -10,7 +10,7 @@
* Processes help agents understand how features work through the codebase.
*/
import type { GraphNode, GraphRelationship, NodeLabel } from 'gitnexus-shared';
import type { GraphNode, NodeLabel } from 'gitnexus-shared';
import { KnowledgeGraph } from '../graph/types.js';
import { CommunityMembership } from './community-processor.js';
import { calculateEntryPointScore, isTestFile } from './entry-point-scoring.js';

View file

@ -883,7 +883,7 @@ export const buildTypeEnv = (
// Most languages use 'name' field; Rust uses 'pattern'; TS uses 'pattern' for some param types.
// Kotlin `parameter` nodes use positional children instead of named fields,
// so we fall back to scanning children by type when childForFieldName returns null.
let typeNode = node.childForFieldName('type');
const typeNode = node.childForFieldName('type');
if (typeNode) {
const nameNode =
node.childForFieldName('name') ??

View file

@ -3,7 +3,6 @@ import type {
ParameterExtractor,
TypeBindingExtractor,
InitializerExtractor,
ClassNameLookup,
ConstructorBindingScanner,
ReturnTypeExtractor,
PendingAssignmentExtractor,

View file

@ -15,7 +15,7 @@
import fs from 'fs/promises';
import { createWriteStream, WriteStream } from 'fs';
import path from 'path';
import type { GraphNode, NodeLabel } from 'gitnexus-shared';
import type { GraphNode } from 'gitnexus-shared';
import { KnowledgeGraph } from '../graph/types.js';
import { NodeTableName } from './schema.js';

View file

@ -20,7 +20,6 @@ import {
touchWikiDb,
getFilesWithExports,
getAllFiles,
getInterFileCallEdges,
getIntraModuleCallEdges,
getInterModuleCallEdges,
getProcessesForFiles,

View file

@ -1169,7 +1169,7 @@ export class LocalBackend {
const symId = sym.id || sym[0];
// Categorized incoming refs
let incomingRows = await executeParameterized(
const incomingRows = await executeParameterized(
repo.id,
`
MATCH (caller)-[r:CodeRelation]->(n {id: $symId})

View file

@ -6,7 +6,6 @@
*/
import { execFileSync } from 'child_process';
import path from 'path';
export interface StalenessInfo {
isStale: boolean;

View file

@ -5,12 +5,7 @@
* without touching the filesystem or LadybugDB.
*/
import { createKnowledgeGraph } from '../../src/core/graph/graph.js';
import type {
KnowledgeGraph,
GraphNode,
NodeLabel,
RelationshipType,
} from '../../src/core/graph/types.js';
import type { KnowledgeGraph, NodeLabel, RelationshipType } from '../../src/core/graph/types.js';
export interface TestNodeInput {
id: string;

View file

@ -14,7 +14,6 @@ import { SupportedLanguages } from '../../src/config/supported-languages.js';
import { getProvider } from '../../src/core/ingestion/languages/index.js';
import {
findEnclosingClassId,
DEFINITION_CAPTURE_KEYS,
getDefinitionNodeFromCaptures,
} from '../../src/core/ingestion/utils/ast-helpers.js';

View file

@ -6,10 +6,9 @@
* PHP export detection (#20), symbol ID with startLine (#19),
* definition node range (#22).
*/
import { describe, it, expect, beforeAll } from 'vitest';
import { describe, it, expect } from 'vitest';
import fs from 'fs/promises';
import path from 'path';
import { createKnowledgeGraph } from '../../src/core/graph/graph.js';
import { loadParser, loadLanguage } from '../../src/core/tree-sitter/parser-loader.js';
import { getLanguageFromFilename, SupportedLanguages } from 'gitnexus-shared';
import { getProvider } from '../../src/core/ingestion/languages/index.js';

View file

@ -5,7 +5,6 @@ import {
getRelationships,
getNodesByLabel,
getNodesByLabelFull,
edgeSet,
runPipelineFromRepo,
type PipelineResult,
} from './helpers.js';

View file

@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';

View file

@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
// Mock all the heavy imports before importing index
vi.mock('../../src/cli/analyze.js', () => ({

View file

@ -3,7 +3,6 @@ import {
preprocessCobolSource,
extractCobolSymbolsWithRegex,
} from '../../src/core/ingestion/cobol/cobol-preprocessor.js';
import type { CobolRegexResults } from '../../src/core/ingestion/cobol/cobol-preprocessor.js';
import { parseReplacingClause } from '../../src/core/ingestion/cobol/cobol-copy-expander.js';
// ---------------------------------------------------------------------------

View file

@ -3,7 +3,6 @@ import Parser from 'tree-sitter';
import { SupportedLanguages } from '../../src/config/supported-languages.js';
import { loadParser, loadLanguage } from '../../src/core/tree-sitter/parser-loader.js';
import { typeConfig } from '../../src/core/ingestion/type-extractors/dart.js';
import { findChild } from '../../src/core/ingestion/utils/ast-helpers.js';
function loadDartOrSkip() {
return loadLanguage(SupportedLanguages.Dart).catch(() => null);

View file

@ -6,10 +6,7 @@ import { pythonConfig } from '../../src/core/ingestion/field-extractors/configs/
import { goConfig } from '../../src/core/ingestion/field-extractors/configs/go.js';
import { cppConfig } from '../../src/core/ingestion/field-extractors/configs/c-cpp.js';
import { rubyConfig } from '../../src/core/ingestion/field-extractors/configs/ruby.js';
import type {
FieldExtractorContext,
ExtractedFields,
} from '../../src/core/ingestion/field-types.js';
import type { FieldExtractorContext } from '../../src/core/ingestion/field-types.js';
import type { TypeEnvironment } from '../../src/core/ingestion/type-env.js';
import { createSymbolTable } from '../../src/core/ingestion/symbol-table.js';
import Parser from 'tree-sitter';

View file

@ -4,7 +4,7 @@
* Tests isGitRepo, getCurrentCommit, getGitRoot, and the newly added
* hasGitDir helper introduced for issue #384 (indexing non-git folders).
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect } from 'vitest';
import path from 'path';
import os from 'os';
import fs from 'fs';

View file

@ -305,7 +305,7 @@ describe('impact: batching and grouping', () => {
(sum: number, call: any[]) => sum + (Array.isArray(call[2]?.ids) ? call[2].ids.length : 0),
0,
);
// eslint-disable-next-line no-console
expect(totalModuleIds).toBe(300);
// Affected modules should include ModuleA

View file

@ -1,6 +1,5 @@
import { describe, it, expect } from 'vitest';
import { parseJcl } from '../../src/core/ingestion/cobol/jcl-parser.js';
import type { JclParseResults } from '../../src/core/ingestion/cobol/jcl-parser.js';
describe('parseJcl', () => {
// ── JOB statements ──────────────────────────────────────────────────

View file

@ -4,7 +4,7 @@
* Tests: getStoragePath, getStoragePaths, readRegistry, registerRepo, unregisterRepo
* Covers hardening fixes #29 (API key file permissions) and #30 (case-insensitive paths on Windows)
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import path from 'path';
import os from 'os';
import fs from 'fs/promises';
@ -12,7 +12,6 @@ import {
getStoragePath,
getStoragePaths,
readRegistry,
saveCLIConfig,
loadCLIConfig,
} from '../../src/storage/repo-manager.js';
import { createTempDir } from '../helpers/test-db.js';

View file

@ -12,7 +12,7 @@
* NOTE: We test the server handler logic by calling the request handlers
* directly through the MCP Server's handler dispatch.
*/
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { createMCPServer } from '../../src/mcp/server.js';
// ─── Mock backend ──────────────────────────────────────────────────

View file

@ -6,7 +6,7 @@
* - HEAD differs stale with commit count
* - Git failure fail open (not stale)
*/
import { describe, it, expect, vi, afterEach } from 'vitest';
import { describe, it, expect } from 'vitest';
import { execFileSync } from 'child_process';
import { checkStaleness } from '../../src/mcp/staleness.js';

View file

@ -8,7 +8,7 @@
* - Optional repo parameter is present on tools that need it
*/
import { describe, it, expect } from 'vitest';
import { GITNEXUS_TOOLS, type ToolDefinition } from '../../src/mcp/tools.js';
import { GITNEXUS_TOOLS } from '../../src/mcp/tools.js';
describe('GITNEXUS_TOOLS', () => {
it('exports all tools (7 base + 3 route/tool/shape + 1 api_impact)', () => {

2024
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,15 +4,27 @@
"scripts": {
"prepare": "husky",
"format": "prettier --write .",
"format:check": "prettier --check ."
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:fix": "eslint --fix ."
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.57.2",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-unused-imports": "^4.4.1",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"prettier": "^3.8.0",
"prettier-plugin-tailwindcss": "^0.7.0"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,mjs,json,css,yml,yaml}": "prettier --write"
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{js,jsx,mjs,json,css,yml,yaml}": "prettier --write"
}
}