From d2be99adf70570c125acb376ffd996f6655055b1 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:37:43 +0100 Subject: [PATCH 1/2] chore(lint): migrate eslint to flat config with naming-convention policy - replace tseslint.config + manual @typescript-eslint/parser with defineConfig/globalIgnores, scoping lint to **/*.ts so `eslint .` skips unconfigured js/mjs/cjs - add structured @typescript-eslint/naming-convention selectors (PascalCase imports/consts, null format for external-data object keys) in place of the blanket warn - drop now-redundant `eslint-disable naming-convention` comments across timeline, soql, and salesforce modules the new policy permits - remove @typescript-eslint/parser dep (now transitive via typescript-eslint) and switch the lint script to `eslint . --cache` --- apex-log-parser/src/ApexLogParser.ts | 4 +- apex-log-parser/src/types.ts | 1 + eslint.config.mjs | 65 ++++++++++++++----- lana-docs/docusaurus.config.ts | 2 +- lana/src/__tests__/mocks/vscode.ts | 6 +- lana/src/commands/ShowInLogAnalysis.ts | 4 +- .../src/salesforce/codesymbol/SymbolFinder.ts | 2 +- lana/src/salesforce/logs/GetLogFile.ts | 2 +- lana/src/salesforce/logs/GetLogFiles.ts | 2 +- .../salesforce/logs/SalesforceConnection.ts | 2 +- log-viewer/src/core/events/EventBus.ts | 2 +- .../messaging/VSCodeExtensionMessenger.ts | 4 +- .../src/features/soql/services/SOQLParser.ts | 22 +++---- .../timeline/__tests__/markers.test.ts | 4 +- .../timeline/optimised/ApexLogTimeline.ts | 3 +- .../features/timeline/services/Timeline.ts | 2 - .../timeline/types/flamechart.types.ts | 15 ++--- .../module/__mocks__/tabulator-tables.ts | 2 +- package.json | 3 +- pnpm-lock.yaml | 3 - 20 files changed, 87 insertions(+), 63 deletions(-) diff --git a/apex-log-parser/src/ApexLogParser.ts b/apex-log-parser/src/ApexLogParser.ts index 29496ef5..b3371699 100644 --- a/apex-log-parser/src/ApexLogParser.ts +++ b/apex-log-parser/src/ApexLogParser.ts @@ -152,8 +152,8 @@ export class ApexLogParser { const hascrlf = log.indexOf('\r\n', startIndex) > -1; let lastEntry = null; - let lfIndex = null; - let eolIndex = (lfIndex = log.indexOf('\n', startIndex)); + let lfIndex = log.indexOf('\n', startIndex); + let eolIndex = lfIndex; let crlfIndex = -1; while (eolIndex !== -1) { diff --git a/apex-log-parser/src/types.ts b/apex-log-parser/src/types.ts index 6c0f732f..bfce15e3 100644 --- a/apex-log-parser/src/types.ts +++ b/apex-log-parser/src/types.ts @@ -116,6 +116,7 @@ export interface SelfTotal { total: number; } +// eslint-disable-next-line no-useless-assignment -- only read in a type position (LogEventType above), which the rule's runtime scope analysis can't see const _logEventNames = [ 'ADD_SCREEN_POP_ACTION', 'ADD_SKILL_REQUIREMENT_ACTION', diff --git a/eslint.config.mjs b/eslint.config.mjs index 861ab2c7..44f45544 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,25 +1,60 @@ import eslint from '@eslint/js'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import prettierConfig from 'eslint-config-prettier/flat'; import tseslint from 'typescript-eslint'; -import tsParser from '@typescript-eslint/parser'; -import prettierConfig from 'eslint-config-prettier'; - -export default tseslint.config( - eslint.configs.recommended, - tseslint.configs.recommended, - prettierConfig, +export default defineConfig( + globalIgnores([ + '**/.sf/', + '**/.sfdx/', + '**/dist/', + '**/build/', + '**/out/', + '**/coverage/', + '**/.docusaurus/', + // only TypeScript is linted; without this, `eslint .` selects js/mjs/cjs + // by default and scans them with no rules + '**/*.js', + '**/*.mjs', + '**/*.cjs', + ]), { - ignores: ['**/node_modules', '**/.sf', '**/.sfdx'], - languageOptions: { - parser: tsParser, - ecmaVersion: 'latest', - sourceType: 'module', - }, + files: ['**/*.ts'], + extends: [eslint.configs.recommended, tseslint.configs.recommended, prettierConfig], rules: { 'no-console': 'warn', - '@typescript-eslint/naming-convention': 'warn', - semi: 'warn', + '@typescript-eslint/naming-convention': [ + 'warn', + // options replace the rule's defaults, so the base selectors are restated + { + selector: 'default', + format: ['camelCase'], + leadingUnderscore: 'allow', + trailingUnderscore: 'allow', + }, + { selector: 'import', format: ['camelCase', 'PascalCase'] }, + { selector: 'typeLike', format: ['PascalCase'] }, + // PascalCase consts: enum-like objects (TimelineErrorCode) and vscode API + // mocks (Uri); allowSingleOrDouble covers __dirname + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE', 'PascalCase'], + leadingUnderscore: 'allowSingleOrDouble', + trailingUnderscore: 'allow', + }, + // UPPER_CASE static readonly class constants (MAX_CACHE_SIZE, DRAG_THRESHOLD) + { + selector: 'classProperty', + modifiers: ['static', 'readonly'], + format: ['camelCase', 'UPPER_CASE'], + }, + // object keys mirror external data: Salesforce API fields, Apex log + // event/category names, module paths, ANTLR rule names + { selector: ['objectLiteralProperty', 'objectLiteralMethod'], format: null }, + // quoted type keys, e.g. 'context-menu' in HTMLElementTagNameMap + { selector: 'typeProperty', modifiers: ['requiresQuotes'], format: null }, + ], '@typescript-eslint/no-unused-vars': [ 'error', diff --git a/lana-docs/docusaurus.config.ts b/lana-docs/docusaurus.config.ts index b1a5aa88..58821953 100644 --- a/lana-docs/docusaurus.config.ts +++ b/lana-docs/docusaurus.config.ts @@ -155,7 +155,7 @@ const config: Config = { { href: `https://github.com/${organizationName}/${projectName}`, position: 'right', - 'aria-label': 'GitHub Repository', // eslint-disable-line @typescript-eslint/naming-convention + 'aria-label': 'GitHub Repository', className: 'header-github-link', }, ], diff --git a/lana/src/__tests__/mocks/vscode.ts b/lana/src/__tests__/mocks/vscode.ts index f3b2aa12..3ba56ddf 100644 --- a/lana/src/__tests__/mocks/vscode.ts +++ b/lana/src/__tests__/mocks/vscode.ts @@ -324,12 +324,12 @@ export const window = { dispose: jest.fn(), })), setStatusBarMessage: jest.fn(() => ({ dispose: jest.fn() })), - withProgress: jest.fn((options, task) => task({ report: jest.fn() })), + withProgress: jest.fn((_options, task) => task({ report: jest.fn() })), }; // Mock commands export const commands = { - registerCommand: jest.fn((command: string, callback: (...args: unknown[]) => unknown) => { + registerCommand: jest.fn((_command: string, _callback: (...args: unknown[]) => unknown) => { const disposable = { dispose: jest.fn() }; subscriptions.push(disposable); return disposable; @@ -340,7 +340,7 @@ export const commands = { // Mock languages export const languages = { - registerFoldingRangeProvider: jest.fn((selector, provider) => { + registerFoldingRangeProvider: jest.fn((_selector, _provider) => { const disposable = { dispose: jest.fn() }; subscriptions.push(disposable); return disposable; diff --git a/lana/src/commands/ShowInLogAnalysis.ts b/lana/src/commands/ShowInLogAnalysis.ts index df0dfd28..7a87a6bc 100644 --- a/lana/src/commands/ShowInLogAnalysis.ts +++ b/lana/src/commands/ShowInLogAnalysis.ts @@ -29,7 +29,7 @@ export class ShowInLogAnalysis { return; } - let panel = LogView.getCurrentView(); + const panel = LogView.getCurrentView(); const logPath = LogView.getLogPath(); // If panel doesn't exist, open the log analysis view first @@ -44,7 +44,7 @@ export class ShowInLogAnalysis { // Set pending navigation so it's sent after log is parsed LogView.setPendingNavigation(timestamp); - panel = await LogView.createView(context, Promise.resolve(), logFilePath); + await LogView.createView(context, Promise.resolve(), logFilePath); return; // Navigation will happen via fetchLog payload } else { // Panel exists - reveal it first diff --git a/lana/src/salesforce/codesymbol/SymbolFinder.ts b/lana/src/salesforce/codesymbol/SymbolFinder.ts index ddb4703a..a2862f00 100644 --- a/lana/src/salesforce/codesymbol/SymbolFinder.ts +++ b/lana/src/salesforce/codesymbol/SymbolFinder.ts @@ -18,7 +18,7 @@ export class SymbolFinder { */ async findSymbol(workspaces: VSWorkspace[], symbol: string): Promise { // Dynamic import for code splitting. Improves performance by reducing the amount of JS that is loaded and parsed at the start. - // eslint-disable-next-line @typescript-eslint/naming-convention + const { Workspaces } = await import('@apexdevtools/apex-ls'); const paths = []; for (const ws of workspaces) { diff --git a/lana/src/salesforce/logs/GetLogFile.ts b/lana/src/salesforce/logs/GetLogFile.ts index 12a735aa..8948be89 100644 --- a/lana/src/salesforce/logs/GetLogFile.ts +++ b/lana/src/salesforce/logs/GetLogFile.ts @@ -8,7 +8,7 @@ export class GetLogFile { const connection = await getSalesforceConnection(wsPath); // Dynamic import for code splitting. Improves performance by reducing the amount of JS that is loaded and parsed at the start. - // eslint-disable-next-line @typescript-eslint/naming-convention + const { LogService } = await import('@salesforce/apex-node'); await new LogService(connection).getLogs({ logId: logId, outputDir: logDir }); } diff --git a/lana/src/salesforce/logs/GetLogFiles.ts b/lana/src/salesforce/logs/GetLogFiles.ts index 7852dd3f..363485ba 100644 --- a/lana/src/salesforce/logs/GetLogFiles.ts +++ b/lana/src/salesforce/logs/GetLogFiles.ts @@ -10,7 +10,7 @@ export class GetLogFiles { const connection = await getSalesforceConnection(wsPath); // Dynamic import for code splitting. Improves performance by reducing the amount of JS that is loaded and parsed at the start. - // eslint-disable-next-line @typescript-eslint/naming-convention + const { LogService } = await import('@salesforce/apex-node'); return new LogService(connection).getLogRecords(); } diff --git a/lana/src/salesforce/logs/SalesforceConnection.ts b/lana/src/salesforce/logs/SalesforceConnection.ts index 397f2520..524ab6ba 100644 --- a/lana/src/salesforce/logs/SalesforceConnection.ts +++ b/lana/src/salesforce/logs/SalesforceConnection.ts @@ -10,7 +10,7 @@ export async function getSalesforceConnection(wsPath: string): Promise(message: string, payload?: T): Promise { const reqId = crypto.randomUUID(); return new Promise((resolve, reject) => { - const listener = (incomingPayload: any, error: unknown) => { + const listener = (incomingPayload: unknown, error: unknown) => { if (error) { reject(error); } else { - resolve(incomingPayload); + resolve(incomingPayload as T); } VSCodeExtensionMessenger.listeners.delete(reqId); }; diff --git a/log-viewer/src/features/soql/services/SOQLParser.ts b/log-viewer/src/features/soql/services/SOQLParser.ts index d1008df2..d349e562 100644 --- a/log-viewer/src/features/soql/services/SOQLParser.ts +++ b/log-viewer/src/features/soql/services/SOQLParser.ts @@ -18,33 +18,33 @@ export class SOQLTree { isSimpleSelect(): boolean { const selectList = this._queryContext.selectList(); const selectEntries = selectList.selectEntry_list(); - return selectEntries.every((selectEntry) => selectEntry.fieldName() != null); + return selectEntries.every((selectEntry) => selectEntry.fieldName() !== null); } /* Return true for queries only containing WHERE, ORDER BY & LIMIT clauses */ isTrivialQuery(): boolean { return ( - this._queryContext.usingScope() == null && - this._queryContext.withClause() == null && - this._queryContext.groupByClause() == null && - this._queryContext.offsetClause() == null && - this._queryContext.allRowsClause() == null && + this._queryContext.usingScope() === null && + this._queryContext.withClause() === null && + this._queryContext.groupByClause() === null && + this._queryContext.offsetClause() === null && + this._queryContext.allRowsClause() === null && this._queryContext.forClauses().getChildCount() === 0 && - this._queryContext.updateList() == null + this._queryContext.updateList() === null ); } /* Return true if query has ORDER BY */ isOrdered(): boolean { - return this._queryContext.orderByClause() != null; + return this._queryContext.orderByClause() !== null; } /* Return limit value if defined, maybe a number or a bound expression */ limitValue(): number | string | undefined { const limitClause = this._queryContext.limitClause(); - if (limitClause == null) { + if (limitClause === null) { return undefined; - } else if (limitClause.IntegerLiteral() != null) { + } else if (limitClause.IntegerLiteral() !== null) { return parseInt(limitClause.IntegerLiteral()?.getText() as string); } else { return limitClause.boundExpression()?.getText() as string; @@ -66,7 +66,7 @@ export class SOQLTree { export class SOQLParser { async parse(query: string): Promise { // Dynamic import for code splitting. Improves performance by reducing the amount of JS that is loaded and parsed at the start. - // eslint-disable-next-line @typescript-eslint/naming-convention + const { ApexParserFactory, ApexErrorListener } = await import('@apexdevtools/apex-parser'); class ThrowingErrorListener extends ApexErrorListener { diff --git a/log-viewer/src/features/timeline/__tests__/markers.test.ts b/log-viewer/src/features/timeline/__tests__/markers.test.ts index 18acc855..55ba7433 100644 --- a/log-viewer/src/features/timeline/__tests__/markers.test.ts +++ b/log-viewer/src/features/timeline/__tests__/markers.test.ts @@ -57,13 +57,13 @@ jest.mock('pixi.js', () => { const actual = jest.requireActual('pixi.js'); return { ...(actual as Record), - // eslint-disable-next-line @typescript-eslint/naming-convention + Sprite: jest.fn().mockImplementation(() => { const mock = new MockSprite(); createdMockSpritesGlobal.push(mock); return mock; }), - // eslint-disable-next-line @typescript-eslint/naming-convention + Texture: { WHITE: {}, }, diff --git a/log-viewer/src/features/timeline/optimised/ApexLogTimeline.ts b/log-viewer/src/features/timeline/optimised/ApexLogTimeline.ts index a1427251..01b37e7b 100644 --- a/log-viewer/src/features/timeline/optimised/ApexLogTimeline.ts +++ b/log-viewer/src/features/timeline/optimised/ApexLogTimeline.ts @@ -361,7 +361,7 @@ export class ApexLogTimeline { private themeToColors(themeName: string) { const theme = getTheme(themeName); // Convert TimelineColors keys to the format expected by FlameChart - /* eslint-disable @typescript-eslint/naming-convention */ + return { Apex: theme.apex, 'Code Unit': theme.codeUnit, @@ -372,7 +372,6 @@ export class ApexLogTimeline { Callout: theme.callout, Validation: theme.validation, }; - /* eslint-enable @typescript-eslint/naming-convention */ } // ============================================================================ diff --git a/log-viewer/src/features/timeline/services/Timeline.ts b/log-viewer/src/features/timeline/services/Timeline.ts index 00a9cd75..21bf2f3d 100644 --- a/log-viewer/src/features/timeline/services/Timeline.ts +++ b/log-viewer/src/features/timeline/services/Timeline.ts @@ -46,7 +46,6 @@ export const keyMap: Map = new Map([ ['SOQL', { label: 'SOQL', fillColor: '#6D4C7D' }], ]); -/* eslint-disable @typescript-eslint/naming-convention */ const LEGACY_CATEGORY_MAP: Record = { Apex: 'Method', 'Code Unit': 'Code Unit', @@ -57,7 +56,6 @@ const LEGACY_CATEGORY_MAP: Record = { Callout: 'Method', Validation: 'System Method', }; -/* eslint-enable @typescript-eslint/naming-convention */ class State { public isRedrawQueued = true; diff --git a/log-viewer/src/features/timeline/types/flamechart.types.ts b/log-viewer/src/features/timeline/types/flamechart.types.ts index 322775fa..d5206c46 100644 --- a/log-viewer/src/features/timeline/types/flamechart.types.ts +++ b/log-viewer/src/features/timeline/types/flamechart.types.ts @@ -418,7 +418,7 @@ export interface CulledRenderData { * Timeline rendering constants. * Using UPPER_CASE naming for constants (standard convention). */ -/* eslint-disable @typescript-eslint/naming-convention */ + export const TIMELINE_CONSTANTS = { /** Height of each event rectangle in pixels. */ EVENT_HEIGHT: 15, @@ -452,7 +452,6 @@ export const TIMELINE_CONSTANTS = { RESIZE_DELAY_MS: 200, }, } as const; -/* eslint-enable @typescript-eslint/naming-convention */ // ============================================================================ // BUCKET RENDERING CONSTANTS @@ -461,7 +460,7 @@ export const TIMELINE_CONSTANTS = { /** * Constants for sub-pixel bucket rendering (barcode pattern). */ -/* eslint-disable @typescript-eslint/naming-convention */ + export const BUCKET_CONSTANTS = { /** Total bucket width in pixels (block + gap) */ BUCKET_WIDTH: 2, @@ -500,7 +499,6 @@ export const BUCKET_CONSTANTS = { 'Validation', ] as const, } as const; -/* eslint-enable @typescript-eslint/naming-convention */ /** * Type for category names in priority order. @@ -628,7 +626,7 @@ export const MARKER_ALPHA = 0.2; * Constants for text label rendering on timeline rectangles. * Used by TextLabelRenderer for LOD-based visibility and truncation. */ -/* eslint-disable @typescript-eslint/naming-convention */ + export const TEXT_LABEL_CONSTANTS = { /** Minimum rectangle width (px) to display any text */ MIN_VISIBLE_WIDTH: 12, @@ -666,7 +664,6 @@ export const TEXT_LABEL_CONSTANTS = { LIGHT_THEME_COLOR: 0x1e1e1e, }, } as const; -/* eslint-enable @typescript-eslint/naming-convention */ /** * Severity levels in ascending order (lowest to highest). @@ -977,7 +974,7 @@ export interface SegmentTreeQueryResult { /** * Constants for segment tree construction and traversal. */ -/* eslint-disable @typescript-eslint/naming-convention */ + export const SEGMENT_TREE_CONSTANTS = { /** * Branching factor for tree construction. @@ -992,7 +989,6 @@ export const SEGMENT_TREE_CONSTANTS = { */ MIN_NODE_SPAN: 1, } as const; -/* eslint-enable @typescript-eslint/naming-convention */ // ============================================================================ // INTERACTION MODE TYPES @@ -1002,13 +998,12 @@ export const SEGMENT_TREE_CONSTANTS = { * Types of drag interactions in the timeline. * Used for unified mode handling in TimelineInteractionHandler. */ -/* eslint-disable @typescript-eslint/naming-convention */ + export const InteractionModeType = { MEASURE: 'measure', AREA_ZOOM: 'areaZoom', RESIZE: 'resize', } as const; -/* eslint-enable @typescript-eslint/naming-convention */ export type InteractionModeType = (typeof InteractionModeType)[keyof typeof InteractionModeType]; diff --git a/log-viewer/src/tabulator/module/__mocks__/tabulator-tables.ts b/log-viewer/src/tabulator/module/__mocks__/tabulator-tables.ts index 55dc36a1..d095170e 100644 --- a/log-viewer/src/tabulator/module/__mocks__/tabulator-tables.ts +++ b/log-viewer/src/tabulator/module/__mocks__/tabulator-tables.ts @@ -1,6 +1,6 @@ // __mocks__/tabulator-tables.ts export class Module { - constructor(table?: any) { + constructor(_table?: unknown) { // this.table = table; } registerTableOption() {} diff --git a/package.json b/package.json index de48d399..de08c3d0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "@swc/helpers": "^0.5.23", "@swc/jest": "^0.2.39", "@types/jest": "^30.0.0", - "@typescript-eslint/parser": "^8.61.1", "concurrently": "^10.0.3", "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", @@ -46,7 +45,7 @@ "copy:package-docs": "node ./scripts/copy-package-docs.mjs", "typecheck": "tsc -b", "typecheck:tsc6": "tsc6 -b", - "lint": "concurrently -r -g 'eslint **/*.ts' 'prettier --cache **/*.{ts,css,md,scss} --check --experimental-cli' 'pnpm run typecheck'", + "lint": "concurrently -r -g 'eslint . --cache --cache-location node_modules/.cache/eslint/' 'prettier --cache **/*.{ts,css,md,scss} --check --experimental-cli' 'pnpm run typecheck'", "test": "jest", "test:ci": "jest --runInBand", "ci:install": "pnpm install --frozen-lockfile --prefer-offline --ignore-scripts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bcf7aff7..68abb139 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,9 +58,6 @@ importers: '@types/jest': specifier: ^30.0.0 version: 30.0.0 - '@typescript-eslint/parser': - specifier: ^8.61.1 - version: 8.61.1(@typescript/typescript6@6.0.1)(eslint@10.5.0(jiti@1.21.7)) concurrently: specifier: ^10.0.3 version: 10.0.3 From ba8f0567579c9060846cdc2b44a0e8f359d4de88 Mon Sep 17 00:00:00 2001 From: Luke Cotter <4013877+lukecotter@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:39:26 +0100 Subject: [PATCH 2/2] chore(lint): remove eslint naming convention disable comments --- log-viewer/src/features/call-tree/utils/CategoryColoring.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/log-viewer/src/features/call-tree/utils/CategoryColoring.ts b/log-viewer/src/features/call-tree/utils/CategoryColoring.ts index ebeef6df..30bc129b 100644 --- a/log-viewer/src/features/call-tree/utils/CategoryColoring.ts +++ b/log-viewer/src/features/call-tree/utils/CategoryColoring.ts @@ -20,7 +20,6 @@ import { addCustomThemes, getTheme } from '../../timeline/themes/ThemeSelector.j // Maps a LogEvent.category string to its TimelineColors key, so a row can point at the // matching `--ct-color-` host variable. Single source of truth for the set. -/* eslint-disable @typescript-eslint/naming-convention */ const CATEGORY_THEME_VAR: Readonly> = { Apex: 'apex', System: 'system', @@ -31,7 +30,6 @@ const CATEGORY_THEME_VAR: Readonly> = { Validation: 'validation', Callout: 'callout', }; -/* eslint-enable @typescript-eslint/naming-convention */ export const categoryColoringStyles = css` .tabulator-row .datagrid-code-text {