Skip to content

Commit 634bf21

Browse files
committed
quality: autofix sort-source-methods + cascade canonical script fixes
Picks up the new autofixable sort-source-methods rule from socket-wheelhouse and the identifier-based _inject-import.js fix (resolves task #65 / #64). Iterated `oxlint --fix` to convergence; function declarations re-ordered into private→export alphanumeric order across the repo. Function declarations are hoisted so the rewrite is safe at runtime; leading JSDoc / line-comments and trailing c8-ignore-stop markers travel with each function. Also re-syncs the canonical scripts/check-paths.mts and scripts/ai-lint-fix.mts from socket-wheelhouse. The wheelhouse copy already accounts for state-machine null sentinels (blockKey, blockKind, inString) and the SKIP_AI_FIX bracket-env access.
1 parent 6b67674 commit 634bf21

53 files changed

Lines changed: 904 additions & 734 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/esbuild.config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const externalDependencies = Object.keys(packageJson.dependencies || {})
5151
* (file uploads) and 'application/json' (API calls). Replaced with a
5252
* minimal lookup covering just those types.
5353
*/
54-
function createLibStubPlugin() {
54+
export function createLibStubPlugin() {
5555
// Heavy lib modules that are eagerly required but never exercised
5656
// by the SDK's actual code paths.
5757
//

.config/oxlint-plugin/rules/_inject-import.js

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,84 @@
1212
* insertion safely — only one survives.
1313
*/
1414

15+
/**
16+
* Build the fixer-side inserts for missing import + optional hoist.
17+
* Returns an array of fixer operations the caller appends to its own
18+
* fix() return value.
19+
*
20+
* summary — output of summarizeImportTarget()
21+
* fixer — the fixer passed to context.report({ fix })
22+
* importLine — the literal `import { ... } from '...'` text
23+
* hoistLine — optional; the literal `const x = ...()` text
24+
*/
25+
export function appendImportFixes(summary, fixer, importLine, hoistLine) {
26+
const ops = []
27+
if (!summary.hasImport) {
28+
if (summary.lastImport) {
29+
ops.push(fixer.insertTextAfter(summary.lastImport, `\n${importLine}`))
30+
} else {
31+
ops.push(fixer.insertTextBeforeRange([0, 0], `${importLine}\n`))
32+
}
33+
}
34+
if (hoistLine && !summary.hasLocal) {
35+
if (summary.lastImport) {
36+
ops.push(fixer.insertTextAfter(summary.lastImport, `\n\n${hoistLine}`))
37+
} else {
38+
ops.push(fixer.insertTextBeforeRange([0, 0], `${hoistLine}\n\n`))
39+
}
40+
}
41+
return ops
42+
}
43+
1544
/**
1645
* Walk a Program node body once and figure out:
1746
* - the last top-level ImportDeclaration node (or undefined)
18-
* - whether `importName` is already imported from `specifier`
47+
* - whether `importName` is already imported (from ANY source)
1948
* - whether a top-level `localName` identifier already exists
2049
* (any const/let/var or import-as-local with that name)
50+
*
51+
* Import detection ignores the specifier path: a file inside the lib
52+
* package itself imports `getDefaultLogger` from `'../logger'`, while
53+
* a downstream repo imports the same name from
54+
* `'@socketsecurity/lib/logger'`. Both resolve to the same identifier;
55+
* either should count as "already imported" so the autofix doesn't
56+
* inject a duplicate (and broken — see issue #64).
57+
*
58+
* `specifier` is retained in the signature for backward compatibility
59+
* but is no longer used for the match decision. Callers may pass any
60+
* truthy value (typically the canonical package path the rule would
61+
* inject if the import were missing).
2162
*/
22-
export function summarizeImportTarget(program, specifier, importName, localName) {
63+
export function summarizeImportTarget(
64+
program,
65+
// eslint-disable-next-line no-unused-vars
66+
specifier,
67+
importName,
68+
localName,
69+
) {
2370
let lastImport
2471
let hasImport = false
2572
let hasLocal = false
2673
for (const stmt of program.body) {
2774
if (stmt.type === 'ImportDeclaration') {
2875
lastImport = stmt
29-
const source = stmt.source && stmt.source.value
30-
if (source === specifier) {
31-
for (const spec of stmt.specifiers) {
32-
if (
33-
spec.type === 'ImportSpecifier' &&
34-
spec.imported &&
35-
spec.imported.name === importName
36-
) {
37-
hasImport = true
38-
}
39-
if (localName && spec.local && spec.local.name === localName) {
40-
hasLocal = true
41-
}
76+
for (const spec of stmt.specifiers) {
77+
if (
78+
spec.type === 'ImportSpecifier' &&
79+
spec.imported &&
80+
spec.imported.name === importName
81+
) {
82+
hasImport = true
83+
}
84+
if (
85+
localName &&
86+
spec.local &&
87+
spec.local.name === localName &&
88+
(spec.type === 'ImportSpecifier' ||
89+
spec.type === 'ImportDefaultSpecifier' ||
90+
spec.type === 'ImportNamespaceSpecifier')
91+
) {
92+
hasLocal = true
4293
}
4394
}
4495
continue
@@ -57,32 +108,3 @@ export function summarizeImportTarget(program, specifier, importName, localName)
57108
}
58109
return { hasImport, hasLocal, lastImport }
59110
}
60-
61-
/**
62-
* Build the fixer-side inserts for missing import + optional hoist.
63-
* Returns an array of fixer operations the caller appends to its own
64-
* fix() return value.
65-
*
66-
* summary — output of summarizeImportTarget()
67-
* fixer — the fixer passed to context.report({ fix })
68-
* importLine — the literal `import { ... } from '...'` text
69-
* hoistLine — optional; the literal `const x = ...()` text
70-
*/
71-
export function appendImportFixes(summary, fixer, importLine, hoistLine) {
72-
const ops = []
73-
if (!summary.hasImport) {
74-
if (summary.lastImport) {
75-
ops.push(fixer.insertTextAfter(summary.lastImport, `\n${importLine}`))
76-
} else {
77-
ops.push(fixer.insertTextBeforeRange([0, 0], `${importLine}\n`))
78-
}
79-
}
80-
if (hoistLine && !summary.hasLocal) {
81-
if (summary.lastImport) {
82-
ops.push(fixer.insertTextAfter(summary.lastImport, `\n\n${hoistLine}`))
83-
} else {
84-
ops.push(fixer.insertTextBeforeRange([0, 0], `${hoistLine}\n\n`))
85-
}
86-
}
87-
return ops
88-
}

.config/oxlint-plugin/rules/export-top-level-functions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const SCRIPT_ENTRY_NAMES = new Set(['main'])
3333
* declarations only via `Program > FunctionDeclaration`; an
3434
* `ExportNamedDeclaration` wraps them in a different shape).
3535
*/
36-
function collectExportedNames(program) {
36+
export function collectExportedNames(program) {
3737
const exported = new Set()
3838
for (const stmt of program.body) {
3939
if (stmt.type === 'ExportNamedDeclaration' && !stmt.declaration) {

.config/oxlint-plugin/rules/inclusive-language.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const REPORT_ONLY_TERMS = ['master', 'slave']
5656
const BYPASS_RE = /inclusive-language:\s*external-api/
5757

5858
/** Build a regex matching any legacy stem with word boundaries. */
59-
function buildDetectorRegex() {
59+
export function buildDetectorRegex() {
6060
const stems = [
6161
...SUBSTITUTIONS.map(([legacy]) => legacy),
6262
...REPORT_ONLY_TERMS,
@@ -72,7 +72,7 @@ const DETECTOR_RE = buildDetectorRegex()
7272
* the new stem. Returns undefined when there's no autofix-able
7373
* substitution (master/slave).
7474
*/
75-
function rewriteHit(match) {
75+
export function rewriteHit(match) {
7676
const lower = match.toLowerCase()
7777
for (const [legacy, replacement] of SUBSTITUTIONS) {
7878
if (!lower.startsWith(legacy)) {
@@ -93,7 +93,7 @@ function rewriteHit(match) {
9393
return undefined
9494
}
9595

96-
function findHits(text) {
96+
export function findHits(text) {
9797
const hits = []
9898
DETECTOR_RE.lastIndex = 0
9999
let m

.config/oxlint-plugin/rules/no-npx-dlx.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const rule = {
135135
if (next === value) {
136136
// Defensive — if our replace-all became a no-op, don't
137137
// ship an empty fix.
138-
return null
138+
return undefined
139139
}
140140
// Preserve the original quote style.
141141
const raw = sourceCode.getText(node)

.config/oxlint-plugin/rules/no-promise-race-in-loop.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,11 @@
2020
* Reporting only.
2121
*/
2222

23-
const RACE_METHODS = new Set(['race', 'any'])
23+
const RACE_METHODS = new Set(['any', 'race'])
2424

25-
const LOOP_TYPES = new Set([
26-
'ForStatement',
27-
'ForOfStatement',
28-
'ForInStatement',
29-
'WhileStatement',
30-
'DoWhileStatement',
31-
])
25+
const LOOP_TYPES = new Set(['DoWhileStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'WhileStatement'])
3226

33-
function isInsideLoop(node) {
27+
export function isInsideLoop(node) {
3428
let current = node.parent
3529
while (current) {
3630
if (LOOP_TYPES.has(current.type)) {

.config/oxlint-plugin/rules/prefer-async-spawn.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,11 @@
4242
* core APIs. Handled at the .oxlintrc.json ignorePatterns level.
4343
*/
4444

45-
const CHILD_PROCESS_SPECIFIERS = new Set([
46-
'node:child_process',
47-
'child_process',
48-
])
45+
const CHILD_PROCESS_SPECIFIERS = new Set(['child_process', 'node:child_process'])
4946

5047
const LIB_SPECIFIER = '@socketsecurity/lib/spawn'
5148

52-
const BANNED_NAMES = new Set(['spawnSync', 'spawn'])
49+
const BANNED_NAMES = new Set(['spawn', 'spawnSync'])
5350

5451
const BYPASS_RE = /prefer-async-spawn:\s*sync-required/
5552

@@ -131,7 +128,7 @@ const rule = {
131128
BANNED_NAMES.has(s.imported.name),
132129
)
133130
if (banned.length === 0) {
134-
return null
131+
return undefined
135132
}
136133
const others = node.specifiers.filter(
137134
s =>
@@ -142,7 +139,7 @@ const rule = {
142139
if (others.length > 0) {
143140
// Mixed line — leave it alone; a partial rewrite could lose
144141
// the non-banned import.
145-
return null
142+
return undefined
146143
}
147144
// The lib re-exports `spawn` (and the user can wire `as
148145
// spawnSync` themselves if they really need a name). For the

.config/oxlint-plugin/rules/prefer-exists-sync.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,8 @@
3333
import { appendImportFixes, summarizeImportTarget } from './_inject-import.js'
3434

3535
const ACCESS_METHODS = new Set(['access', 'accessSync'])
36-
const STAT_METHODS = new Set(['stat', 'statSync', 'lstat', 'lstatSync'])
37-
const WRAPPER_NAMES = new Set([
38-
'fileExists',
39-
'pathExists',
40-
'isFile',
41-
'isDir',
42-
])
36+
const STAT_METHODS = new Set(['lstat', 'lstatSync', 'stat', 'statSync'])
37+
const WRAPPER_NAMES = new Set(['fileExists', 'isDir', 'isFile', 'pathExists'])
4338

4439
const EXISTS_SYNC_IMPORT_LINE =
4540
"import { existsSync } from 'node:fs'"

.config/oxlint-plugin/rules/prefer-safe-delete.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,9 @@
3333

3434
import { appendImportFixes, summarizeImportTarget } from './_inject-import.js'
3535

36-
const DELETE_METHODS = new Set([
37-
'rm',
38-
'rmSync',
39-
'unlink',
40-
'unlinkSync',
41-
'rmdir',
42-
'rmdirSync',
43-
])
44-
45-
const SYNC_METHODS = new Set(['rmSync', 'unlinkSync', 'rmdirSync'])
36+
const DELETE_METHODS = new Set(['rm', 'rmSync', 'rmdir', 'rmdirSync', 'unlink', 'unlinkSync'])
37+
38+
const SYNC_METHODS = new Set(['rmSync', 'rmdirSync', 'unlinkSync'])
4639

4740
/** @type {import('eslint').Rule.RuleModule} */
4841
const rule = {

.config/oxlint-plugin/rules/socket-api-token-env.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@
2222
* socket-cli configuration setting, not an API token alias.
2323
*/
2424

25-
const LEGACY_ALIASES = new Set([
26-
'SOCKET_API_KEY',
27-
'SOCKET_SECURITY_API_TOKEN',
28-
'SOCKET_SECURITY_API_KEY',
29-
])
25+
const LEGACY_ALIASES = new Set(['SOCKET_API_KEY', 'SOCKET_SECURITY_API_KEY', 'SOCKET_SECURITY_API_TOKEN'])
3026

3127
const CANONICAL = 'SOCKET_API_TOKEN'
3228

0 commit comments

Comments
 (0)