Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
9 changes: 9 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
**/node_modules
**/build
**/system-test
**/test/fixtures
**/samples/generated
**/.coverage
**/coverage
**/baselines
**/baselines-esm
**/.test-out*
test/fixtures
build/
docs/
protos/
packages/
**/types.d.ts
66 changes: 63 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,80 @@
{
"extends": [
"./node_modules/gts",
"gts",
"plugin:prettier/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:promise/recommended"
],
"root": true,
// Note: All rules configured as "error" are blocking in the PR CI pipeline.
// Only rules configured as "warn" remain non-blocking.
"rules": {
"import/no-unresolved": "off",
"import/no-extraneous-dependencies": "error",
"promise/catch-or-return": "error",
"promise/always-return": "error"
},
"overrides": [
// The overrides below were migrated from handwritten/firestore/.eslintrc.json
// during monorepo ESLint consolidation to maintain Firestore-specific rules.
{
"files": ["handwritten/firestore/dev/src/**/*.ts"],
"excludedFiles": ["handwritten/firestore/dev/src/v1/*.ts", "handwritten/firestore/dev/src/v1beta1/*.ts"],
"parser": "@typescript-eslint/parser",
"rules": {
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true,
"allowTypedFunctionExpressions": true
}
],
"no-console": ["error", {"allow": ["error"]}],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
]
}
},
{
"files": ["handwritten/firestore/dev/test/*.ts", "handwritten/firestore/dev/system-test/*.ts"],
"parser": "@typescript-eslint/parser",
"rules": {
"no-restricted-properties": [
"error",
{
"object": "describe",
"property": "only"
},
{
"object": "it",
"property": "only"
}
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-floating-promises": "warn"
}
},
{
"files": [
"handwritten/firestore/dev/src/v1/**/*.ts",
"handwritten/firestore/dev/src/v1beta1/**/*.ts",
"handwritten/firestore/dev/test/gapic_firestore_v1.ts",
"handwritten/firestore/dev/test/gapic_firestore_admin_v1.ts",
"handwritten/firestore/dev/test/gapic_firestore_admin_v1.ts"
],
"rules": {
"@typescript-eslint/no-explicit-any": ["off"],
"@typescript-eslint/no-floating-promises": ["off"]
}
}
],
"ignorePatterns": [
"**/node_modules",
"**/build",
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/presubmit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ jobs:
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 300
fetch-depth: 2
persist-credentials: false
- name: Use Node.js 24
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 24
- run: npm install
- run: npm run lint
- run: node ./bin/linter.mjs --strict
name: Run monorepo linter
env:
GIT_DIFF_ARG: "HEAD^1...HEAD"
146 changes: 113 additions & 33 deletions bin/linter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ import {existsSync} from 'fs';
import path from 'path';
import {promisify} from 'util';
import {ESLint} from 'eslint';
import ts from 'typescript';

// --- Globals & Promisified API Wrappers ---
const execFileAsync = promisify(execFile);
const tsconfigCache = new Map();

// --- Main Runner (Entry Point) ---
async function run() {
try {
const changedTsFiles = getChangedFiles();
const isStrict = Boolean(process.argv.includes('--strict'));
let changedTsFiles;
if (isStrict) {
changedTsFiles = getChangedFilesStrict();
} else {
changedTsFiles = getChangedFiles();
}

if (changedTsFiles.length === 0) {
console.log('No TypeScript files changed. Skipping checks.');
Expand Down Expand Up @@ -63,6 +67,50 @@ function runGit(args, options = {}) {
});
}

function getChangedFilesStrict() {
let gitDiffArg = process.env.GIT_DIFF_ARG;

if (!gitDiffArg) {
throw new Error(
'Strict mode is enabled, but GIT_DIFF_ARG environment variable or --git-diff-arg flag was not provided. ' +
'Please set the GIT_DIFF_ARG environment variable or provide --git-diff-arg <arg>.'
);
}

// If a single ref is provided (e.g. "HEAD^1" or "origin/main"), convert to three-dot diff ("ref...HEAD")
// to compare against the merge-base and avoid listing files modified on the base branch.
if (!gitDiffArg.includes('..')) {
gitDiffArg = `${gitDiffArg}...HEAD`;
}

console.log(`Strict mode enabled. Comparing using GIT_DIFF_ARG: ${gitDiffArg}`);

const args = gitDiffArg.trim().split(/\s+/);

try {
const output = runGit([
'diff',
'--name-only',
'--diff-filter=ACMRT',
...args,
'--',
'*.ts',
]);
return output
.split('\n')
.map(f => f.trim())
.filter(f => f.length > 0 && existsSync(f));
} catch (err) {
if (err.status !== 1) {
throw new Error(
`Strict mode error: git diff ${gitDiffArg} failed with exit code ${err.status}.\n` +
`Ensure that the git reference '${gitDiffArg}' exists locally and that you have fetched the required commits/branches.\n` +
`Details: ${String(err.stderr || err.message || '').trim()}`
);
}
}
}

/**
* Returns a list of changed TypeScript files comparing against target branches/references.
*/
Expand All @@ -79,11 +127,12 @@ function getChangedFiles() {

for (const ref of refsToTry) {
try {
const diffRef = ref.includes('..') ? ref : `${ref}...HEAD`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this change needed for anything?

const output = runGit([
'diff',
'--name-only',
'--diff-filter=ACMRT',
ref,
diffRef,
'--',
'*.ts',
]);
Expand Down Expand Up @@ -126,37 +175,59 @@ async function checkEslint(filesToCheck) {
return true;
}

try {
const eslint = new ESLint();
const results = await eslint.lintFiles(filesToCheck);
const formatter = await eslint.loadFormatter('stylish');
const resultText = formatter.format(results);

if (resultText) {
console.log(resultText);
// Group files by package directory to set tsconfigRootDir properly for typescript-eslint
const filesByPkg = new Map();
for (const file of filesToCheck) {
const pkgDir = findTsconfigDir(file) || process.cwd();
if (!filesByPkg.has(pkgDir)) {
filesByPkg.set(pkgDir, []);
}
filesByPkg.get(pkgDir).push(file);
}

let hasBlockingErrors = false;
let hasBlockingErrors = false;

for (const fileResult of results) {
for (const message of fileResult.messages) {
// message.severity === 2 indicates an error-level rule configuration.
if (message.severity === 2) {
hasBlockingErrors = true;
}
for (const [pkgDir, files] of filesByPkg.entries()) {
try {
const absPkgDir = path.resolve(pkgDir);
const eslint = new ESLint({
cwd: absPkgDir,
resolvePluginsRelativeTo: process.cwd(),
overrideConfig: {
parserOptions: {
tsconfigRootDir: absPkgDir,
},
},
});

const relativeFiles = files.map(f => path.relative(absPkgDir, path.resolve(f)));
const results = await eslint.lintFiles(relativeFiles);
const formatter = await eslint.loadFormatter('stylish');
const resultText = formatter.format(results);

if (resultText) {
console.log(resultText);
}
}

if (hasBlockingErrors) {
console.error('\n[ERROR] ESLint violations were detected.');
return false;
for (const fileResult of results) {
for (const message of fileResult.messages) {
if (message.severity === 2) {
hasBlockingErrors = true;
}
}
}
} catch (err) {
console.error(`\n[ERROR] Failed running ESLint in ${pkgDir}:`, err.message);
hasBlockingErrors = true;
}
}

return true;
} catch (err) {
console.error('\n[ERROR] Failed running ESLint:', err.message);
if (hasBlockingErrors) {
console.error('\n[ERROR] ESLint violations were detected.');
return false;
}

return true;
}

// --- TypeScript Type Checker ---
Expand All @@ -166,14 +237,23 @@ async function checkEslint(filesToCheck) {
* Caches directories to avoid redundant disk operations.
*/
function findTsconfigDir(filePath) {
const dir = path.dirname(filePath);
if (tsconfigCache.has(dir)) {
return tsconfigCache.get(dir);
let currentDir = path.resolve(path.dirname(filePath));
const root = path.parse(currentDir).root;

while (currentDir && currentDir !== root) {
if (tsconfigCache.has(currentDir)) {
return tsconfigCache.get(currentDir);
}
const candidate = path.join(currentDir, 'tsconfig.json');
if (existsSync(candidate)) {
tsconfigCache.set(path.dirname(filePath), currentDir);
return currentDir;
}
currentDir = path.dirname(currentDir);
}
const configPath = ts.findConfigFile(dir, ts.sys.fileExists);
const result = configPath ? path.dirname(configPath) : null;
tsconfigCache.set(dir, result);
return result;

tsconfigCache.set(path.dirname(filePath), null);
return null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "./node_modules/gts/"
"extends": "gts"
}
3 changes: 1 addition & 2 deletions core/generator/gapic-generator-typescript/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@
"**/.coverage",
"**/coverage"
]
}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "./node_modules/gts"
"extends": "gts"
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "./node_modules/gts"
"extends": "gts"
}
3 changes: 0 additions & 3 deletions core/packages/gaxios/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions core/packages/google-auth-library-nodejs/.eslintrc.json

This file was deleted.

4 changes: 0 additions & 4 deletions core/packages/logging-utils/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions core/packages/nodejs-googleapis-common/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions core/packages/nodejs-proto-files/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions core/packages/retry-request/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions core/packages/teeny-request/.eslintrc.json

This file was deleted.

4 changes: 0 additions & 4 deletions core/packages/tools/.eslintrc.json

This file was deleted.

3 changes: 0 additions & 3 deletions handwritten/bigquery-storage/.eslintrc.json

This file was deleted.

1 change: 0 additions & 1 deletion handwritten/bigquery/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ build/
docs/
protos/
samples/generated/
system-test/fixtures
3 changes: 1 addition & 2 deletions handwritten/bigquery/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"extends": "./node_modules/gts",
"root": true
"extends": "./node_modules/gts"
}
3 changes: 0 additions & 3 deletions handwritten/cloud-profiler/.eslintrc.json

This file was deleted.

Loading
Loading