Skip to content
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
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