perf(lint): avoid unnecessary anti-slop scope construction (#28470)

* perf(lint): construct anti-slop scopes only for relevant expressions

* fix(ci): resolve extensionless mobile census entries as files
This commit is contained in:
Neil
2026-10-13 18:19:50 -07:00
committed by GitHub
parent 7b31e739d2
commit 83d096b5f5
4 changed files with 131 additions and 3 deletions
@@ -30,3 +30,49 @@ index af5f08a5002fb26f94414be13f1ad848ae73d522..eb95ebc44fcb4b4760b2afe097a114cb
environment = createTypeAliasEnvironment(
node,
context.sourceCode.visitorKeys,
diff --git a/src/rules/no-widen-then-assert.ts b/src/rules/no-widen-then-assert.ts
--- a/src/rules/no-widen-then-assert.ts
+++ b/src/rules/no-widen-then-assert.ts
@@ -325,11 +325,12 @@
},
},
createOnce(context) {
- let scopes: Parameters<typeof resolvedVariableForIdentifier>[0] = [];
+ let scopes: Parameters<typeof resolvedVariableForIdentifier>[0] | null = null;
const checkAssertion = (node: ESTree.TSAsExpression | ESTree.TSTypeAssertion) => {
const expression = assertedExpression(node);
if (expression.type !== "Identifier") return;
+ scopes ??= context.sourceCode.scopeManager.scopes;
const variable = resolvedVariableForIdentifier(scopes, expression);
if (variable === null) return;
@@ -357,7 +358,7 @@
return {
Program() {
- scopes = context.sourceCode.scopeManager.scopes;
+ scopes = null;
},
TSAsExpression: checkAssertion,
TSTypeAssertion: checkAssertion,
diff --git a/src/rules/no-module-mocking.ts b/src/rules/no-module-mocking.ts
--- a/src/rules/no-module-mocking.ts
+++ b/src/rules/no-module-mocking.ts
@@ -39,7 +39,6 @@
function moduleMockCall(sourceCode: SourceCode, callee: ESTree.Expression): boolean {
if (!("property" in callee) || !("object" in callee) || !("computed" in callee)) return false;
- if (!isTestFrameworkObject(sourceCode, callee.object)) return false;
const property = callee.property;
const method = callee.computed
? property.type === "Literal" &&
@@ -51,7 +50,7 @@
: property.type === "Identifier"
? property.name
: null;
- return method !== null && moduleMockMethods.has(method);
+ return method !== null && moduleMockMethods.has(method) && isTestFrameworkObject(sourceCode, callee.object);
}
/** Ban test framework module mocking in favor of real dependency seams. */
@@ -0,0 +1,48 @@
import { cpSync, mkdtempSync, rmSync } from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { afterAll, expect, it } from 'vitest'
import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs'
const directory = mkdtempSync(path.join(import.meta.dirname, '.anti-slop-scope-'))
const source = path.dirname(createRequire(import.meta.url).resolve('oxlint-plugin-anti-slop'))
cpSync(source, directory, { recursive: true })
afterAll(() => rmSync(directory, { recursive: true, force: true }))
function lint(source, rule) {
return runOxlintPluginOnSource({
pluginName: 'anti-slop',
pluginPath: path.join(directory, 'index.ts'),
rules: { [`anti-slop/${rule}`]: 'error' },
source,
extension: 'ts'
})
}
it.each([
['const value = {}; const wide: unknown = value; wide as { id: string };', 1],
['const value = {}; const alias = value; const wide: unknown = alias; <{ id: string }>wide;', 1],
['function parse(input: unknown) { return input as { id: string }; }', 0],
['const value = {}; let wide: unknown = value; wide as { id: string };', 0],
['const value = {}; const wide: unknown = value; function read() { return wide as {}; }', 0],
[
'const value = {}; const wide: unknown = value; function read(wide: unknown) { return wide as {}; }',
0
],
['const value = {}; const wide: unknown = value; wide as unknown;', 0]
])('preserves widening evidence and lexical boundaries: %s', (source, count) => {
expect(lint(source, 'no-widen-then-assert')).toHaveLength(count)
})
it.each([
["import { vi as test } from 'vitest'; test.mock('module');", 1],
["import { jest as test } from '@jest/globals'; test['doMock']('module');", 1],
["jest.unstable_mockModule('module', () => ({}));", 1],
["vi['mock']('module');", 1],
["import { vi } from 'vitest'; vi.mocked(value);", 0],
["const unrelated = { mock() {} }; unrelated.mock('module');", 0],
["const vi = { mock() {} }; vi.mock('module');", 0],
["import { vi as test } from 'vitest'; function run(test) { test.mock('module'); }", 0]
])('preserves mock method and owner resolution: %s', (source, count) => {
expect(lint(source, 'no-module-mocking')).toHaveLength(count)
})
@@ -0,0 +1,34 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { expect, it } from 'vitest'
import { mobileWebAppModuleClosure } from './build-mobile-web-app-bundle.mjs'
it.each(['extensionless', 'explicit', 'absolute'])(
'resolves %s census entries against their source tree and prefers web siblings',
async (form) => {
const directory = mkdtempSync(path.join(tmpdir(), 'orca-mobile-entry-'))
const modules = ['app/_layout', 'app/h/_layout', 'app/h/[hostId]/index']
const webModules = ['app/_layout.web.tsx', 'app/h/_layout.tsx', 'app/h/[hostId]/index.web.tsx']
try {
for (const file of [...webModules, 'app/_layout.tsx', 'app/h/[hostId]/index.tsx']) {
const target = path.join(directory, file)
mkdirSync(path.dirname(target), { recursive: true })
writeFileSync(target, 'export const entry = 1')
}
const entries = modules.map((entry) => {
if (form === 'explicit') {
return `${entry}.tsx`
}
if (form === 'absolute') {
return path.join(directory, `${entry}.tsx`)
}
return entry
})
const { local } = await mobileWebAppModuleClosure(entries, { absWorkingDir: directory })
expect(local.map((file) => file.replaceAll('\\', '/')).sort()).toEqual(webModules.sort())
} finally {
rmSync(directory, { recursive: true, force: true })
}
}
)
+3 -3
View File
@@ -177,7 +177,7 @@ patchedDependencies:
lint-staged@16.4.0: 7333b3837f80a7fbd045964db6d76ba4fc118e49134bdbabb00585b6b7b60673
monaco-editor@0.55.1: 53b71f7d8a7c00b76ffdb73ce83d18e31692f193f5fafa6a390e48f5b3fdd636
node-pty@1.1.0: c2fe19cde7aef223738a7503ae429dd9b03432130b064aa64a601900b33c151f
oxlint-plugin-anti-slop@0.1.2: c442be3f300c2d47e98af4c6347fc18cad8c13cc18913c4ee50b3720dad85753
oxlint-plugin-anti-slop@0.1.2: c10c871702365a0c2c59e9f1c5fb92df97cae5755d534c0a3d83c6368f6feafe
ws@8.22.0: 9393e04dd9a90b01eb7aa35fcd5b1431b5038ce999fbbd82651136193ba51f14
importers:
@@ -514,7 +514,7 @@ importers:
version: 1.86.0(oxlint-tsgolint@7.0.2003)
oxlint-plugin-anti-slop:
specifier: github:dmmulroy/anti-slop#c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b
version: https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b(patch_hash=c442be3f300c2d47e98af4c6347fc18cad8c13cc18913c4ee50b3720dad85753)
version: https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b(patch_hash=c10c871702365a0c2c59e9f1c5fb92df97cae5755d534c0a3d83c6368f6feafe)
oxlint-plugin-react-doctor:
specifier: 0.9.14
version: 0.9.14
@@ -14171,7 +14171,7 @@ snapshots:
'@oxfmt/binding-win32-ia32-msvc': 0.71.0
'@oxfmt/binding-win32-x64-msvc': 0.71.0
oxlint-plugin-anti-slop@https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b(patch_hash=c442be3f300c2d47e98af4c6347fc18cad8c13cc18913c4ee50b3720dad85753):
oxlint-plugin-anti-slop@https://codeload.github.com/dmmulroy/anti-slop/tar.gz/c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b(patch_hash=c10c871702365a0c2c59e9f1c5fb92df97cae5755d534c0a3d83c6368f6feafe):
dependencies:
'@oxlint/plugins': 1.78.0