Skip to content

Migrate eslint config #214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
36 changes: 1 addition & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,41 +159,7 @@ GuideLLM UI is a companion frontend for visualizing the results of a GuideLLM be

### 🛠 Running the UI

1. Use the Hosted Build (Recommended for Most Users)

After running a benchmark with GuideLLM, a report.html file will be generated (by default at guidellm_report/report.html). This file references the latest stable version of the UI hosted at:

```
https://neuralmagic.github.io/guidellm/ui/dev/
```

Open the file in your browser and you're done—no setup required.

2. Build and Serve the UI Locally (For Development) This option is useful if:

- You are actively developing the UI

- You want to test changes to the UI before publishing

- You want full control over how the report is displayed

```bash
npm install
npm run build
npx serve out
```

This will start a local server (e.g., at http://localhost:3000). Then, in your GuideLLM config or CLI flags, point to this local server as the asset base for report generation.

### 🧪 Development Notes

During UI development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under:

```
src/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts
```

In the future this will be replaced by a configurable untracked file for dev use.
The UI is a WIP, check more recent PRs for the latest updates

## Resources

Expand Down
234 changes: 115 additions & 119 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,72 @@
// @ts-check

import eslint from '@eslint/js';
import typescriptPlugin from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import { FlatCompat } from '@eslint/eslintrc';
import reactPlugin from 'eslint-plugin-react';
import hooksPlugin from 'eslint-plugin-react-hooks';
import nextPlugin from '@next/eslint-plugin-next';
import prettierConfig from 'eslint-config-prettier';
import cypressPlugin from 'eslint-plugin-cypress';
import importPlugin from 'eslint-plugin-import';
import jestPlugin from 'eslint-plugin-jest';
import noSecretsPlugin from 'eslint-plugin-no-secrets';
import prettierPlugin from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';
import reactPlugin from 'eslint-plugin-react';
import hooksPlugin from 'eslint-plugin-react-hooks';
import globals from 'globals';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import tseslint from 'typescript-eslint';

// --- SETUP ---
// Recreate __dirname for ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: eslint.configs.recommended,
});
// --- EXPORT ESLINT CONFIG ---
export default tseslint.config(
// 1. Global Ignores
{
ignores: ['node_modules/', '.next/', 'dist/', 'coverage/', '.DS_Store'],
},

export default [
// Base configuration
// 2. Base Configurations (Applied to all files)
eslint.configs.recommended,
prettierConfig, // Disables ESLint rules that conflict with Prettier. IMPORTANT: Must be after other configs.

// Next.js configuration using FlatCompat
...compat.extends('next/core-web-vitals'),

// --- Main Configuration ---
// 3. Configuration for App Source Code (Next.js with Type-Aware Linting)
{
files: ['src/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}'],
files: ['src/ui/**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
ecmaVersion: 2024,
sourceType: 'module',
parser: tseslint.parser,
parserOptions: {
project: true, // Enable type-aware linting
tsconfigRootDir: __dirname,
},
globals: {
...globals.browser,
...globals.node,
...globals.jest,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
project: [
'./src/ui/tsconfig.json',
'./tsconfig.test.json',
'./tsconfig.cypress.json',
],
tsconfigRootDir: import.meta.dirname,
noWarnOnMultipleProjects: true,
...globals.node, // Add Node.js globals for `process` etc.
},
},
plugins: {
'@typescript-eslint': typescriptPlugin,
'@typescript-eslint': tseslint.plugin,
'@next/next': nextPlugin,
import: importPlugin,
react: reactPlugin,
'react-hooks': hooksPlugin,
import: importPlugin,
jest: jestPlugin,
'no-secrets': noSecretsPlugin,
prettier: prettierPlugin,
},
rules: {
// Ccustom rules
complexity: ['warn', { max: 8 }],
curly: ['error', 'all'],
// --- Base rules to disable in favor of TS versions ---
'no-unused-vars': 'off',

// TypeScript rules
// --- Recommended rules from plugins ---
...tseslint.configs.recommendedTypeChecked.rules,
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
...reactPlugin.configs.recommended.rules,
...hooksPlugin.configs.recommended.rules,

// --- Prettier ---
'prettier/prettier': 'error',

// --- Custom Rules & Overrides ---
'@typescript-eslint/no-unused-vars': [
'warn',
{
Expand All @@ -79,103 +75,103 @@ export default [
caughtErrorsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-explicit-any': 'warn',

// Next.js overrides
'@next/next/no-img-element': 'off', // Allow img tags if needed
'@next/next/no-page-custom-font': 'warn',

// React rules
'react/react-in-jsx-scope': 'off', // Not needed in Next.js
'react/prop-types': 'off', // Using TypeScript
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',

// Import rules
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/*.test.{js,jsx,ts,tsx}',
'**/*.d.ts',
'**/*.interfaces.ts',
'**/*.setup.{js,ts}',
'**/*.config.{js,mjs,ts}',
'tests/**/*',
'cypress/**/*',
],
optionalDependencies: false,
peerDependencies: false,
},
],
'import/order': [
'error',
{
groups: [
['builtin', 'external'],
['internal', 'parent', 'sibling', 'index'],
],
'newlines-between': 'always-and-inside-groups',
pathGroups: [
{
pattern:
'@{app,assets,classes,components,hooks,lib,pages,store,tests,types,utils}/**',
group: 'internal',
position: 'before',
},
{
pattern: '{.,..}/**',
group: 'internal',
position: 'after',
},
],
pathGroupsExcludedImportTypes: ['builtin'],
groups: [['builtin', 'external'], 'internal', ['parent', 'sibling', 'index']],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
},
],

// Security
'no-secrets/no-secrets': ['error', { additionalRegexes: {}, ignoreContent: [] }],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',

// Prettier
'prettier/prettier': 'error',
'@next/next/no-html-link-for-pages': 'off',
'@next/next/no-img-element': 'off',

complexity: ['warn', { max: 8 }],
},
settings: {
next: {
rootDir: ['src/ui/', 'tests/ui/'],
},
'import/resolver': {
typescript: {
project: [
'./src/ui/tsconfig.json',
'./tsconfig.test.json',
'./tsconfig.cypress.json',
],
noWarnOnMultipleProjects: true,
},
react: { version: 'detect' },
'import/resolver': { typescript: true, node: true },
},
},

// 4. Configuration for Jest Test Files (Type-Aware)
{
files: ['tests/ui/**/*.{test,spec}.{ts,tsx}', 'jest.setup.ts'],
languageOptions: {
parser: tseslint.parser, // Explicitly set parser
parserOptions: {
project: './tsconfig.test.json',
tsconfigRootDir: __dirname,
},
react: {
version: 'detect',
globals: {
...globals.jest,
...globals.node, // FIX: Add Node.js globals for `global`, etc.
},
},
plugins: {
jest: jestPlugin,
},
rules: {
...jestPlugin.configs['flat/recommended'].rules,
'@typescript-eslint/unbound-method': 'off',
},
},

// Jest-specific rules for test files
// 5. Configuration for Cypress E2E Test Files (Type-Aware)
{
files: [
'tests/**/*.{js,jsx,ts,tsx}',
'**/*.test.{js,jsx,ts,tsx}',
'**/*.spec.{js,jsx,ts,tsx}',
'tests/ui/cypress/**/*.{cy,e2e}.{ts,tsx}',
'tests/ui/cypress/support/**/*.ts',
],
languageOptions: {
parser: tseslint.parser, // Explicitly set parser
parserOptions: {
project: './tsconfig.cypress.json',
tsconfigRootDir: __dirname,
},
// FIX: This is the correct way to get globals from the Cypress plugin's recommended config.
globals: cypressPlugin.configs.recommended.languageOptions.globals,
},
plugins: {
cypress: cypressPlugin,
},
// Apply recommended rules and then add our overrides
rules: {
'jest/expect-expect': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'warn',
'jest/valid-expect': 'error',
...cypressPlugin.configs.recommended.rules,
'jest/expect-expect': 'off',
'jest/no-standalone-expect': 'off',
'@typescript-eslint/no-floating-promises': 'off',
},
},

// Prettier config (disables conflicting rules)
prettierConfig,
];
// 6. Configuration for JS/TS config files
{
files: ['**/*.config.{js,mjs,ts}'],
languageOptions: {
globals: {
...globals.node,
},
},
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},

// 7. Configuration for JS/TS mock files and test helpers
{
files: ['tests/ui/**/__mocks__/**/*.{js,ts}', 'tests/ui/unit/mocks/**/*.ts'],
languageOptions: {
globals: {
...globals.node,
},
},
}
);
1 change: 0 additions & 1 deletion jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ jest.mock('next/dynamic', () => ({
const dynamicModule = jest.requireActual('next/dynamic');
const dynamicActualComp = dynamicModule.default;
const RequiredComponent = dynamicActualComp(props[0]);
// eslint-disable-next-line no-unused-expressions, @typescript-eslint/no-unused-expressions
RequiredComponent.preload
? RequiredComponent.preload()
: RequiredComponent.render.preload();
Expand Down
Loading