2025-04-18 17:44:24 -07:00
/ * *
* @ license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-04-17 17:57:39 -04:00
import eslint from '@eslint/js' ;
import tseslint from 'typescript-eslint' ;
import reactPlugin from 'eslint-plugin-react' ;
import reactHooks from 'eslint-plugin-react-hooks' ;
import prettierConfig from 'eslint-config-prettier' ;
import importPlugin from 'eslint-plugin-import' ;
2025-08-25 16:21:47 +02:00
import vitest from '@vitest/eslint-plugin' ;
2025-04-17 17:57:39 -04:00
import globals from 'globals' ;
2026-01-01 11:01:03 -08:00
import headers from 'eslint-plugin-headers' ;
2025-08-25 22:11:27 +02:00
import path from 'node:path' ;
2025-04-21 08:02:11 -07:00
import url from 'node:url' ;
// --- ESM way to get __dirname ---
const _ _filename = url . fileURLToPath ( import . meta . url ) ;
const _ _dirname = path . dirname ( _ _filename ) ;
// --- ---
// Determine the monorepo root (assuming eslint.config.js is at the root)
const projectRoot = _ _dirname ;
2026-02-10 14:06:17 -05:00
const currentYear = new Date ( ) . getFullYear ( ) ;
2025-04-17 17:57:39 -04:00
2026-03-03 11:17:34 -05:00
const commonRestrictedSyntaxRules = [
{
selector : 'CallExpression[callee.name="require"]' ,
message : 'Avoid using require(). Use ES6 imports instead.' ,
} ,
{
selector : 'ThrowStatement > Literal:not([value=/^\\w+Error:/])' ,
message :
'Do not throw string literals or non-Error objects. Throw new Error("...") instead.' ,
} ,
2026-03-09 12:02:07 -07:00
{
selector : 'CallExpression[callee.name="fetch"]' ,
message :
'Use safeFetch() from "@/utils/fetch" instead of the global fetch() to ensure SSRF protection. If you are implementing a custom security layer, use an eslint-disable comment and explain why.' ,
} ,
2026-03-03 11:17:34 -05:00
] ;
2025-04-17 17:57:39 -04:00
export default tseslint . config (
{
// Global ignores
2025-04-19 19:45:42 +01:00
ignores : [
2025-05-06 15:48:26 +00:00
'node_modules/*' ,
2025-04-19 19:45:42 +01:00
'eslint.config.js' ,
2025-08-26 20:49:25 +00:00
'packages/**/dist/**' ,
2025-05-23 12:27:48 -07:00
'bundle/**' ,
2025-08-03 16:19:34 -04:00
'package/bundle/**' ,
2025-08-06 15:26:46 -04:00
'.integration-tests/**' ,
2025-09-10 01:28:58 -07:00
'dist/**' ,
2026-01-14 04:49:17 +00:00
'evals/**' ,
'packages/test-utils/**' ,
2026-02-19 16:47:35 -08:00
'.gemini/skills/**' ,
2025-04-19 19:45:42 +01:00
] ,
2025-04-17 17:57:39 -04:00
} ,
eslint . configs . recommended ,
... tseslint . configs . recommended ,
2025-05-06 15:48:26 +00:00
reactHooks . configs [ 'recommended-latest' ] ,
reactPlugin . configs . flat . recommended ,
reactPlugin . configs . flat [ 'jsx-runtime' ] , // Add this if you are using React 17+
2025-05-20 12:53:27 -07:00
{
// Settings for eslint-plugin-react
settings : {
react : {
version : 'detect' ,
} ,
} ,
} ,
2025-04-17 17:57:39 -04:00
{
2026-02-28 02:38:34 +05:30
// Rules for packages/*/src (TS/TSX)
files : [ 'packages/*/src/**/*.{ts,tsx}' ] ,
2025-07-22 00:22:13 +01:00
plugins : {
import : importPlugin ,
} ,
settings : {
'import/resolver' : {
node : true ,
} ,
} ,
2025-04-17 17:57:39 -04:00
languageOptions : {
2025-12-02 07:11:40 +09:00
parser : tseslint . parser ,
parserOptions : {
projectService : true ,
tsconfigRootDir : projectRoot ,
} ,
2025-04-17 17:57:39 -04:00
globals : {
... globals . node ,
... globals . es2021 ,
} ,
} ,
rules : {
2026-02-28 02:38:34 +05:30
... importPlugin . configs . recommended . rules ,
... importPlugin . configs . typescript . rules ,
'import/no-default-export' : 'warn' ,
'import/no-unresolved' : 'off' ,
'import/no-duplicates' : 'error' ,
2025-04-17 17:57:39 -04:00
// General Best Practice Rules (subset adapted for flat config)
'@typescript-eslint/array-type' : [ 'error' , { default : 'array-simple' } ] ,
'arrow-body-style' : [ 'error' , 'as-needed' ] ,
curly : [ 'error' , 'multi-line' ] ,
eqeqeq : [ 'error' , 'always' , { null : 'ignore' } ] ,
'@typescript-eslint/consistent-type-assertions' : [
'error' ,
{ assertionStyle : 'as' } ,
] ,
'@typescript-eslint/explicit-member-accessibility' : [
'error' ,
{ accessibility : 'no-public' } ,
] ,
2025-05-23 22:35:50 +00:00
'@typescript-eslint/no-explicit-any' : 'error' ,
2025-04-17 17:57:39 -04:00
'@typescript-eslint/no-inferrable-types' : [
'error' ,
{ ignoreParameters : true , ignoreProperties : true } ,
] ,
2025-08-26 00:04:53 +02:00
'@typescript-eslint/consistent-type-imports' : [
'error' ,
{ disallowTypeAnnotations : false } ,
] ,
2025-04-17 17:57:39 -04:00
'@typescript-eslint/no-namespace' : [ 'error' , { allowDeclarations : true } ] ,
'@typescript-eslint/no-unused-vars' : [
2025-05-06 15:48:26 +00:00
'error' ,
2025-04-17 18:06:21 -04:00
{
argsIgnorePattern : '^_' ,
varsIgnorePattern : '^_' ,
caughtErrorsIgnorePattern : '^_' ,
} ,
2025-04-17 17:57:39 -04:00
] ,
2025-12-02 07:11:40 +09:00
// Prevent async errors from bypassing catch handlers
'@typescript-eslint/return-await' : [ 'error' , 'in-try-catch' ] ,
2026-02-24 16:47:37 -05:00
'import/no-internal-modules' : 'off' ,
2025-07-22 00:22:13 +01:00
'import/no-relative-packages' : 'error' ,
2025-04-17 17:57:39 -04:00
'no-cond-assign' : 'error' ,
'no-debugger' : 'error' ,
'no-duplicate-case' : 'error' ,
2026-03-07 21:05:38 +00:00
'no-restricted-syntax' : [
'error' ,
... commonRestrictedSyntaxRules ,
{
selector :
'UnaryExpression[operator="typeof"] > MemberExpression[computed=true][property.type="Literal"]' ,
message :
'Do not use typeof to check object properties. Define a TypeScript interface and a type guard function instead.' ,
} ,
] ,
2025-04-17 17:57:39 -04:00
'no-unsafe-finally' : 'error' ,
'no-unused-expressions' : 'off' , // Disable base rule
2025-04-17 18:06:21 -04:00
'@typescript-eslint/no-unused-expressions' : [
// Enable TS version
2025-04-17 17:57:39 -04:00
'error' ,
{ allowShortCircuit : true , allowTernary : true } ,
] ,
'no-var' : 'error' ,
'object-shorthand' : 'error' ,
'one-var' : [ 'error' , 'never' ] ,
'prefer-arrow-callback' : 'error' ,
'prefer-const' : [ 'error' , { destructuring : 'all' } ] ,
radix : 'error' ,
2025-12-29 15:46:10 -05:00
'no-console' : 'error' ,
2025-04-17 17:57:39 -04:00
'default-case' : 'error' ,
2025-12-16 21:28:18 -08:00
'@typescript-eslint/await-thenable' : [ 'error' ] ,
2025-12-05 16:12:49 -08:00
'@typescript-eslint/no-floating-promises' : [ 'error' ] ,
2025-12-12 17:43:43 -08:00
'@typescript-eslint/no-unnecessary-type-assertion' : [ 'error' ] ,
2026-01-06 20:09:39 -08:00
'no-restricted-imports' : [
'error' ,
{
paths : [
{
name : 'node:os' ,
importNames : [ 'homedir' , 'tmpdir' ] ,
message :
'Please use the helpers from @google/gemini-cli-core instead of node:os homedir()/tmpdir() to ensure strict environment isolation.' ,
} ,
{
name : 'os' ,
importNames : [ 'homedir' , 'tmpdir' ] ,
message :
'Please use the helpers from @google/gemini-cli-core instead of os homedir()/tmpdir() to ensure strict environment isolation.' ,
} ,
] ,
} ,
] ,
} ,
} ,
2026-03-03 11:17:34 -05:00
{
// API Response Optionality enforcement for Code Assist
files : [ 'packages/core/src/code_assist/**/*.{ts,tsx}' ] ,
rules : {
'no-restricted-syntax' : [
'error' ,
... commonRestrictedSyntaxRules ,
{
selector :
'TSInterfaceDeclaration[id.name=/.+Response$/] TSPropertySignature:not([optional=true])' ,
message :
'All fields in API response interfaces (*Response) must be marked as optional (?) to prevent developers from accidentally assuming a field will always be present based on current backend behavior.' ,
} ,
{
selector :
'TSTypeAliasDeclaration[id.name=/.+Response$/] TSPropertySignature:not([optional=true])' ,
message :
'All fields in API response types (*Response) must be marked as optional (?) to prevent developers from accidentally assuming a field will always be present based on current backend behavior.' ,
} ,
] ,
} ,
} ,
2026-02-10 00:10:15 +00:00
{
// Rules that only apply to product code
files : [ 'packages/*/src/**/*.{ts,tsx}' ] ,
ignores : [ '**/*.test.ts' , '**/*.test.tsx' ] ,
rules : {
'@typescript-eslint/no-unsafe-type-assertion' : 'error' ,
2026-02-20 22:28:55 +00:00
'@typescript-eslint/no-unsafe-assignment' : 'error' ,
2026-02-21 01:12:56 +00:00
'@typescript-eslint/no-unsafe-return' : 'error' ,
2026-02-10 00:10:15 +00:00
} ,
} ,
2026-01-06 20:09:39 -08:00
{
// Allow os.homedir() in tests and paths.ts where it is used to implement the helper
files : [
'**/*.test.ts' ,
'**/*.test.tsx' ,
'packages/core/src/utils/paths.ts' ,
'packages/test-utils/src/**/*.ts' ,
'scripts/**/*.js' ,
] ,
rules : {
'no-restricted-imports' : 'off' ,
2025-04-17 17:57:39 -04:00
} ,
} ,
2025-10-30 13:15:49 -07:00
{
// Prevent self-imports in packages
files : [ 'packages/core/src/**/*.{ts,tsx}' ] ,
rules : {
'no-restricted-imports' : [
'error' ,
{
name : '@google/gemini-cli-core' ,
message : 'Please use relative imports within the @google/gemini-cli-core package.' ,
} ,
] ,
} ,
} ,
{
files : [ 'packages/cli/src/**/*.{ts,tsx}' ] ,
rules : {
'no-restricted-imports' : [
'error' ,
{
name : '@google/gemini-cli' ,
message : 'Please use relative imports within the @google/gemini-cli package.' ,
} ,
] ,
} ,
} ,
2026-02-12 22:08:27 -08:00
{
files : [ 'packages/sdk/src/**/*.{ts,tsx}' ] ,
rules : {
'no-restricted-imports' : [
'error' ,
{
name : '@google/gemini-cli-sdk' ,
message : 'Please use relative imports within the @google/gemini-cli-sdk package.' ,
} ,
] ,
} ,
} ,
2025-08-25 16:21:47 +02:00
{
files : [ 'packages/*/src/**/*.test.{ts,tsx}' ] ,
plugins : {
vitest ,
} ,
rules : {
... vitest . configs . recommended . rules ,
'vitest/expect-expect' : 'off' ,
'vitest/no-commented-out-tests' : 'off' ,
2026-03-07 21:05:38 +00:00
'no-restricted-syntax' : [ 'error' , ... commonRestrictedSyntaxRules ] ,
2025-08-25 16:21:47 +02:00
} ,
} ,
2025-04-20 17:16:25 -07:00
{
2026-02-10 00:06:16 +05:30
files : [ './**/*.{tsx,ts,js,cjs}' ] ,
2025-04-20 17:16:25 -07:00
plugins : {
2026-01-01 11:01:03 -08:00
headers ,
2025-08-25 22:11:27 +02:00
import : importPlugin ,
2025-04-20 17:16:25 -07:00
} ,
rules : {
2026-01-01 11:01:03 -08:00
'headers/header-format' : [
2025-04-20 17:16:25 -07:00
'error' ,
2026-01-01 11:01:03 -08:00
{
source : 'string' ,
content : [
'@license' ,
'Copyright (year) Google LLC' ,
'SPDX-License-Identifier: Apache-2.0' ,
] . join ( '\n' ) ,
patterns : {
year : {
2026-02-10 14:06:17 -05:00
pattern : ` 202[5- ${ currentYear . toString ( ) . slice ( - 1 ) } ] ` ,
defaultValue : currentYear . toString ( ) ,
2026-01-01 11:01:03 -08:00
} ,
} ,
} ,
2025-04-20 17:16:25 -07:00
] ,
2025-08-25 22:11:27 +02:00
'import/enforce-node-protocol-usage' : [ 'error' , 'always' ] ,
2025-04-20 17:16:25 -07:00
} ,
} ,
2025-04-21 23:10:25 -07:00
{
2025-06-08 02:05:55 -07:00
files : [ './scripts/**/*.js' , 'esbuild.config.js' ] ,
2025-04-21 23:10:25 -07:00
languageOptions : {
globals : {
2025-06-13 18:08:03 -07:00
... globals . node ,
2025-04-21 23:10:25 -07:00
process : 'readonly' ,
console : 'readonly' ,
} ,
} ,
2025-06-13 18:08:03 -07:00
rules : {
'@typescript-eslint/no-unused-vars' : [
'error' ,
{
argsIgnorePattern : '^_' ,
varsIgnorePattern : '^_' ,
caughtErrorsIgnorePattern : '^_' ,
} ,
] ,
} ,
2025-04-21 23:10:25 -07:00
} ,
2026-02-10 00:06:16 +05:30
{
files : [ '**/*.cjs' ] ,
languageOptions : {
sourceType : 'commonjs' ,
globals : {
... globals . node ,
} ,
} ,
rules : {
'no-restricted-syntax' : 'off' ,
'no-console' : 'off' ,
'no-empty' : 'off' ,
'no-redeclare' : 'off' ,
'@typescript-eslint/no-require-imports' : 'off' ,
'@typescript-eslint/no-unused-vars' : [
'error' ,
{
argsIgnorePattern : '^_' ,
varsIgnorePattern : '^_' ,
caughtErrorsIgnorePattern : '^_' ,
} ,
] ,
} ,
} ,
2025-07-14 15:34:44 +00:00
{
files : [ 'packages/vscode-ide-companion/esbuild.js' ] ,
languageOptions : {
globals : {
... globals . node ,
process : 'readonly' ,
console : 'readonly' ,
} ,
} ,
rules : {
'no-restricted-syntax' : 'off' ,
'@typescript-eslint/no-require-imports' : 'off' ,
} ,
} ,
2026-01-20 12:14:46 -05:00
// Examples should have access to standard globals like fetch
{
files : [ 'packages/cli/src/commands/extensions/examples/**/*.js' ] ,
languageOptions : {
globals : {
... globals . node ,
fetch : 'readonly' ,
} ,
} ,
} ,
2025-08-03 16:19:34 -04:00
// extra settings for scripts that we run directly with node
{
files : [ 'packages/vscode-ide-companion/scripts/**/*.js' ] ,
languageOptions : {
globals : {
... globals . node ,
process : 'readonly' ,
console : 'readonly' ,
} ,
} ,
rules : {
'no-restricted-syntax' : 'off' ,
'@typescript-eslint/no-require-imports' : 'off' ,
} ,
} ,
2025-04-17 17:57:39 -04:00
// Prettier config must be last
prettierConfig ,
2025-06-16 08:27:29 -07:00
// extra settings for scripts that we run directly with node
{
files : [ './integration-tests/**/*.js' ] ,
languageOptions : {
globals : {
... globals . node ,
process : 'readonly' ,
console : 'readonly' ,
} ,
} ,
rules : {
'@typescript-eslint/no-unused-vars' : [
'error' ,
{
argsIgnorePattern : '^_' ,
varsIgnorePattern : '^_' ,
caughtErrorsIgnorePattern : '^_' ,
} ,
] ,
} ,
} ,
2025-04-17 17:57:39 -04:00
) ;