Skip to content

Disallow underspecified types#21485

Merged
gundermanc merged 6 commits intomainfrom
gundermanc/disallow-underspecified-types
Mar 7, 2026
Merged

Disallow underspecified types#21485
gundermanc merged 6 commits intomainfrom
gundermanc/disallow-underspecified-types

Conversation

@gundermanc
Copy link
Member

@gundermanc gundermanc commented Mar 6, 2026

Summary

Adds a new eslint rule that disallows syntax like the following outside of tests.

typeof myObject['myProperty'] == 'string'

Why

This syntax is almost exclusively used in the codebase as a form of inline type assertion for unpacking properties on an object of unknown or Record<string, unknown> type.

I believe that typeof expression == 'string' is a reasonable type assertion, but using this same pattern to check object type params is a recipe for bugs.

  • The property name being checked is a string, and therefore not validated by the compiler. As the underlying code changes, the developer has to be very careful to update every instance of the string to avoid a regression. Usually these usages are at some distance from each other and easily missed.
  • Loosely typed objects like this can't be easily inventoried with Find References in the IDE, making it more difficult to reason about how data passes through the system.
  • Since we are checking just one property, these type assertions don't guard against regressions where the wrong object with a similarly named parameter is allowed to be handled, making it more difficult to reason about failures at runtime.
  • Finally, readability is significantly improved with this new model by reducing repetitive boilerplate in the code and giving it a readable name like isMyObject().

The previously verbose type guards...

  if (
      data &&
      typeof data === 'object' &&
      'access_token' in data &&
      // eslint-disable-next-line no-restricted-syntax
      typeof (data as Record<string, unknown>)['access_token'] === 'string'
    ) {
  }

...can be rewritten as...

  if (isOAuthResponse(data)) {
  }

Alternatives

Instead of the above, I recommend the following:

interface MyObject {
   myProperty: string
   ...
}

// While there is still a string property associated with MyObject, it's done in one place.
function isMyObject(obj: unknown) is obj MyObject {
  if ('myProperty' in obj) {
    return true;
  }
}

When should I use or not use Record<string, unknown>

You can still use Record<string, unknown>, when you know you are dealing with a string keyed unknown object type where you need to dynamically test the value type due to a lack of consistent schema. In general though you should pass objects of unknown type around as unknown and use type guard functions to check the type, as needed. If you know at compile time that an object can be only one of a few different types, or have members of a specific set of types, use discriminated unions and generics to represent these possibilities.

// ❌ Bad -- do not use when the shape of the data is known.
const result: Record<string, unknown> = fetch(...);

// ✅ Good -- if unsure, use unknown and a type guard function, then a custom type or Zod template.
const result: unknown = fetch(...);
const typedResult = isMyResultType(result) ? result : undefined;

Fixes ##21499

@gundermanc gundermanc force-pushed the gundermanc/disallow-underspecified-types branch from d357c26 to c2f47af Compare March 6, 2026 23:29
@github-actions
Copy link

github-actions bot commented Mar 6, 2026

Size Change: +1.46 kB (+0.01%)

Total Size: 26 MB

Filename Size Change
./bundle/gemini.js 25.5 MB +1.46 kB (+0.01%)
ℹ️ View Unchanged
Filename Size
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 221 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 227 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 11.5 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB
./bundle/sandbox-macos-strict-open.sb 4.82 kB
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB

compressed-size-action

@gundermanc gundermanc linked an issue Mar 7, 2026 that may be closed by this pull request
@gundermanc gundermanc marked this pull request as ready for review March 7, 2026 00:15
@gundermanc gundermanc requested a review from a team as a code owner March 7, 2026 00:15
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the codebase's type safety and maintainability by introducing a new ESLint rule. The rule targets a common pattern of checking object property types using typeof with bracket notation, which can lead to brittle code due to unvalidated string property names. By disallowing this pattern, the PR encourages developers to adopt more robust type-checking mechanisms like TypeScript interfaces and type guard functions. The changes also include strategically placed ESLint disable comments to allow for a phased refactoring of existing code that currently uses the deprecated pattern.

Highlights

  • New ESLint Rule: Introduced a new no-restricted-syntax ESLint rule that disallows the use of typeof myObject['myProperty'] == 'string' for checking object properties. This rule aims to improve type safety and readability by encouraging the use of TypeScript interfaces and dedicated type guard functions.
  • Codebase Updates: Existing instances of the disallowed typeof check pattern have been identified and temporarily suppressed with // eslint-disable-next-line no-restricted-syntax comments across various files. This allows the new rule to be enforced while acknowledging areas that require future refactoring to align with the new best practice.
  • ESLint Rule Scoping: Refactored existing no-restricted-syntax rules concerning require() and ThrowStatement to apply specifically to integration-tests and evals files, ensuring more targeted linting.
Changelog
  • eslint.config.js
    • Introduced a new no-restricted-syntax rule to prevent typeof checks on computed member expressions with literal properties, promoting the use of type guard functions.
    • Refactored existing no-restricted-syntax rules concerning require() and ThrowStatement to apply specifically to integration-tests and evals files.
  • packages/a2a-server/src/agent/task.ts
    • Added ESLint disable comments for typeof checks on object properties.
  • packages/cli/src/commands/hooks/migrate.ts
    • Added ESLint disable comments for typeof checks on object properties.
  • packages/cli/src/test-utils/mockDebugLogger.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/cli/src/test-utils/render.tsx
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/cli/src/ui/hooks/slashCommandProcessor.ts
    • Added ESLint disable comments for typeof checks on object properties.
  • packages/cli/src/utils/activityLogger.ts
    • Refactored etc[0] access to a named variable arg0 in req.write and req.end methods.
  • packages/core/src/agents/browser/browserAgentInvocation.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/agents/browser/mcpToolWrapper.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/hooks/hookAggregator.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/services/FolderTrustDiscoveryService.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/services/chatCompressionService.ts
    • Added ESLint disable comments for typeof checks on object properties.
  • packages/core/src/services/loopDetectionService.ts
    • Added ESLint disable comments for typeof checks on object properties.
  • packages/core/src/telemetry/semantic.ts
    • Added ESLint disable comments for typeof checks on object properties.
  • packages/core/src/tools/mcp-tool.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/utils/editCorrector.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/utils/googleErrors.ts
    • Added an ESLint disable comment for a typeof check on an object property.
  • packages/core/src/utils/oauth-flow.ts
    • Added ESLint disable comments for typeof checks on object properties.
Activity
  • No specific activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new ESLint rule. My review focuses on a maintainability improvement in the ESLint configuration file itself, where I suggest using a shared constant to avoid duplicating rule definitions.

Note: Security Review is unavailable for this PR.

eslint.config.js Outdated
Comment on lines 275 to 308
'no-restricted-syntax': [
'error',
{
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.',
},
],
},
},
{
files: [
'integration-tests/**/*.ts',
'evals/**/*.ts',
],
rules: {
'no-restricted-syntax': [
'error',
{
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.',
},
],
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To improve maintainability and avoid code duplication, please use the commonRestrictedSyntaxRules constant that is already defined in this file. Both of these new no-restricted-syntax blocks are duplicating the rules from that constant.

      'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules],
    },
  },
  {
    files: [
      'integration-tests/**/*.ts',
      'evals/**/*.ts',
    ],
    rules: {
      'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules],
    },
  },

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable new ESLint rule to disallow checking object property types with typeof obj['property'], guiding developers towards using safer type guard functions. This is a great step for improving type safety and maintainability. The strategy of introducing the rule and disabling it for existing violations is a practical approach for a large codebase. I've found one minor area for improvement in the ESLint configuration to reduce code duplication.

Note: Security Review did not run due to the size of the PR.

eslint.config.js Outdated
Comment on lines +275 to +286
'no-restricted-syntax': [
'error',
{
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.',
},
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To improve maintainability and prevent the configurations from drifting apart, you can reuse the commonRestrictedSyntaxRules constant here instead of duplicating the rules. The same applies to the configuration for integration-tests and evals below.

      'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules],

Copy link
Contributor

@jacob314 jacob314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@gundermanc gundermanc enabled auto-merge March 7, 2026 20:54
@gundermanc gundermanc added this pull request to the merge queue Mar 7, 2026
Merged via the queue into main with commit dac3735 Mar 7, 2026
27 checks passed
@gundermanc gundermanc deleted the gundermanc/disallow-underspecified-types branch March 7, 2026 21:16
kunal-10-cloud pushed a commit to kunal-10-cloud/gemini-cli that referenced this pull request Mar 12, 2026
liamhelmer pushed a commit to badal-io/gemini-cli that referenced this pull request Mar 12, 2026
yashodipmore pushed a commit to yashodipmore/geemi-cli that referenced this pull request Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Disallow underspecified types

3 participants