Skip to content

Commit 77cace4

Browse files
dreddnafiousclaude
andauthored
fix: autodetect src layouts without overriding explicit search paths (#222)
* Enable autoSearchPaths to match pyright's CLI pyright's own CLI sets `autoSearchPaths = true` unconditionally ("Always enable autoSearchPaths when using the command line", pyright-internal/src/common/configOptions.ts consumer in pyright.ts). scip-python replaces that entry point and never sets the field, so `commandLineOptions.autoSearchPaths || false` is always false and ensureDefaultExtraPaths() never adds ./src. The effect is silent: for a src-layout project with no [tool.pyright] section, definitions are emitted under `src.pkg.mod` while imports resolve to `pkg.mod`, so every reference from outside src/ is dropped from the index. The run exits 0 and the index is simply smaller. An explicit extraPaths still takes precedence, so projects that already configure pyright are unaffected. Fixes #221 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: resolve temporary fixture paths on macOS --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 06d728d commit 77cace4

4 files changed

Lines changed: 102 additions & 30 deletions

File tree

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
1-
# < definition scip-python python snapshot-util 0.1 `src.single_class`/__init__:
1+
# < definition scip-python python snapshot-util 0.1 single_class/__init__:
22

33
class ExampleClass:
4-
# ^^^^^^^^^^^^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#
4+
# ^^^^^^^^^^^^ definition snapshot-util 0.1 single_class/ExampleClass#
55
a: int
6-
# ^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#a.
6+
# ^ definition snapshot-util 0.1 single_class/ExampleClass#a.
77
# ^^^ reference python-stdlib 3.11 builtins/int#
88
b: int
9-
# ^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#b.
9+
# ^ definition snapshot-util 0.1 single_class/ExampleClass#b.
1010
# ^^^ reference python-stdlib 3.11 builtins/int#
1111
c: str
12-
# ^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#c.
12+
# ^ definition snapshot-util 0.1 single_class/ExampleClass#c.
1313
# ^^^ reference python-stdlib 3.11 builtins/str#
1414

1515
static_var = "Hello World"
16-
# ^^^^^^^^^^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#static_var.
16+
# ^^^^^^^^^^ definition snapshot-util 0.1 single_class/ExampleClass#static_var.
1717

1818
def __init__(self, a: int, b: int):
19-
# ^^^^^^^^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().
20-
# ^^^^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(self)
21-
# ^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(a)
19+
# ^^^^^^^^ definition snapshot-util 0.1 single_class/ExampleClass#__init__().
20+
# ^^^^ definition snapshot-util 0.1 single_class/ExampleClass#__init__().(self)
21+
# ^ definition snapshot-util 0.1 single_class/ExampleClass#__init__().(a)
2222
# ^^^ reference python-stdlib 3.11 builtins/int#
23-
# ^ definition snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(b)
23+
# ^ definition snapshot-util 0.1 single_class/ExampleClass#__init__().(b)
2424
# ^^^ reference python-stdlib 3.11 builtins/int#
2525
local_c = ", world!"
2626
# ^^^^^^^ definition local 0
2727

2828
self.a = a
29-
# ^^^^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(self)
30-
# ^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#a.
31-
# ^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(a)
29+
# ^^^^ reference snapshot-util 0.1 single_class/ExampleClass#__init__().(self)
30+
# ^ reference snapshot-util 0.1 single_class/ExampleClass#a.
31+
# ^ reference snapshot-util 0.1 single_class/ExampleClass#__init__().(a)
3232
self.b = b
33-
# ^^^^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(self)
34-
# ^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#b.
35-
# ^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(b)
33+
# ^^^^ reference snapshot-util 0.1 single_class/ExampleClass#__init__().(self)
34+
# ^ reference snapshot-util 0.1 single_class/ExampleClass#b.
35+
# ^ reference snapshot-util 0.1 single_class/ExampleClass#__init__().(b)
3636
self.c = "hello" + local_c
37-
# ^^^^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().(self)
38-
# ^ reference snapshot-util 0.1 `src.single_class`/ExampleClass#c.
37+
# ^^^^ reference snapshot-util 0.1 single_class/ExampleClass#__init__().(self)
38+
# ^ reference snapshot-util 0.1 single_class/ExampleClass#c.
3939
# ^^^^^^^ reference local 0
4040

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1-
# < definition scip-python python snapshot-util 0.1 `src.single_function`/__init__:
1+
# < definition scip-python python snapshot-util 0.1 single_function/__init__:
22

33
def my_cool_function(a: str) -> str:
4-
# ^^^^^^^^^^^^^^^^ definition snapshot-util 0.1 `src.single_function`/my_cool_function().
5-
# ^ definition snapshot-util 0.1 `src.single_function`/my_cool_function().(a)
4+
# ^^^^^^^^^^^^^^^^ definition snapshot-util 0.1 single_function/my_cool_function().
5+
# ^ definition snapshot-util 0.1 single_function/my_cool_function().(a)
66
# ^^^ reference python-stdlib 3.11 builtins/str#
77
# ^^^ reference python-stdlib 3.11 builtins/str#
88
x = ", world"
99
# ^ definition local 0
1010
return a + x
11-
# ^ reference snapshot-util 0.1 `src.single_function`/my_cool_function().(a)
11+
# ^ reference snapshot-util 0.1 single_function/my_cool_function().(a)
1212
# ^ reference local 0
1313

1414
def my_cool_function_2(a: str):
15-
# ^^^^^^^^^^^^^^^^^^ definition snapshot-util 0.1 `src.single_function`/my_cool_function_2().
16-
# ^ definition snapshot-util 0.1 `src.single_function`/my_cool_function_2().(a)
15+
# ^^^^^^^^^^^^^^^^^^ definition snapshot-util 0.1 single_function/my_cool_function_2().
16+
# ^ definition snapshot-util 0.1 single_function/my_cool_function_2().(a)
1717
# ^^^ reference python-stdlib 3.11 builtins/str#
1818
x = ", world"
1919
# ^ definition local 1
2020
return (lambda y: a + x + y)("oh no")
2121
# ^ definition local 2(y)
22-
# ^ reference snapshot-util 0.1 `src.single_function`/my_cool_function_2().(a)
22+
# ^ reference snapshot-util 0.1 single_function/my_cool_function_2().(a)
2323
# ^ reference local 1
2424
# ^ reference local 2(y)
2525

2626
def next_level():
27-
# ^^^^^^^^^^ definition snapshot-util 0.1 `src.single_function`/next_level().
27+
# ^^^^^^^^^^ definition snapshot-util 0.1 single_function/next_level().
2828
return my_cool_function
29-
# ^^^^^^^^^^^^^^^^ reference snapshot-util 0.1 `src.single_function`/my_cool_function().
29+
# ^^^^^^^^^^^^^^^^ reference snapshot-util 0.1 single_function/my_cool_function().
3030

3131
my_cool_function("hello")
32-
#^^^^^^^^^^^^^^^ reference snapshot-util 0.1 `src.single_function`/my_cool_function().
32+
#^^^^^^^^^^^^^^^ reference snapshot-util 0.1 single_function/my_cool_function().
3333
next_level()(a = "wow")
34-
#^^^^^^^^^ reference snapshot-util 0.1 `src.single_function`/next_level().
35-
# ^ reference snapshot-util 0.1 `src.single_function`/my_cool_function().(a)
34+
#^^^^^^^^^ reference snapshot-util 0.1 single_function/next_level().
35+
# ^ reference snapshot-util 0.1 single_function/my_cool_function().(a)
3636

‎packages/pyright-scip/src/config.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ export class ScipPyrightConfig {
223223
configOptions.applyDiagnosticOverrides(commandLineOptions.diagnosticSeverityOverrides);
224224
}
225225

226+
// Detect conventional src layouts only when search paths are unconfigured.
227+
// In this Pyright version, execution-environment paths append to defaults;
228+
// adding src before initialization would shadow explicit environment paths.
229+
if (configOptions.defaultExtraPaths === undefined && configOptions.executionEnvironments.length === 0) {
230+
configOptions.ensureDefaultExtraPaths(this.fs, true, undefined);
231+
}
232+
226233
// Override the analyzeUnannotatedFunctions setting based on the command-line setting.
227234
if (commandLineOptions.analyzeUnannotatedFunctions !== undefined) {
228235
configOptions.diagnosticRuleSet.analyzeUnannotatedFunctions =

‎packages/pyright-scip/test/test-main.ts‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,70 @@ version = "16.05"`,
274274
}
275275
}
276276

277+
function testSourceSearchPaths(): void {
278+
for (const testCase of [
279+
{ config: undefined, expected: 'src_handler' },
280+
{ config: {}, expected: 'src_handler' },
281+
{ config: { extraPaths: ['custom'] }, expected: 'custom_handler' },
282+
{ config: { executionEnvironments: [{ root: '.', extraPaths: ['custom'] }] }, expected: 'custom_handler' },
283+
{ config: { extraPaths: [] }, expected: undefined },
284+
{ config: undefined, srcIsPackage: true, expected: undefined },
285+
]) {
286+
// macOS temporary directories can be symlinked; match the analyzer's resolved file paths.
287+
const projectRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'scip-search-paths-')));
288+
try {
289+
for (const sourceRoot of ['src', 'custom']) {
290+
const packageRoot = path.join(projectRoot, sourceRoot, 'demo');
291+
fs.mkdirSync(packageRoot, { recursive: true });
292+
fs.writeFileSync(path.join(packageRoot, '__init__.py'), '');
293+
fs.writeFileSync(path.join(packageRoot, 'core.py'), `def ${sourceRoot}_handler():\n return 1\n`);
294+
}
295+
if (testCase.srcIsPackage) {
296+
fs.writeFileSync(path.join(projectRoot, 'src', '__init__.py'), '');
297+
}
298+
if (testCase.config) {
299+
fs.writeFileSync(path.join(projectRoot, 'pyrightconfig.json'), JSON.stringify(testCase.config));
300+
}
301+
fs.writeFileSync(
302+
path.join(projectRoot, 'consumer.py'),
303+
'from demo.core import src_handler, custom_handler\nsrc_handler()\ncustom_handler()\n'
304+
);
305+
indexAction({
306+
projectName: 'search-paths',
307+
projectVersion: '1.0',
308+
cwd: projectRoot,
309+
output: 'index.scip',
310+
environment: path.resolve('snapshots/testEnv.json'),
311+
infer: { projectVersionFromCommit: false },
312+
quiet: true,
313+
dev: false,
314+
showProgressRateLimit: undefined,
315+
});
316+
const index = scip.Index.deserializeBinary(fs.readFileSync(path.join(projectRoot, 'index.scip')));
317+
const consumer = index.documents.find((doc) => doc.relative_path === 'consumer.py')!;
318+
for (const name of ['src_handler', 'custom_handler']) {
319+
const references = consumer.occurrences.filter((occurrence) =>
320+
occurrence.symbol.endsWith(`/${name}().`)
321+
);
322+
assert.strictEqual(references.length, name === testCase.expected ? 2 : 0, JSON.stringify(testCase));
323+
if (references.length > 0) {
324+
assert.ok(
325+
index.documents.some((doc) =>
326+
doc.occurrences.some(
327+
(occurrence) =>
328+
occurrence.symbol === references[0].symbol &&
329+
(occurrence.symbol_roles & scip.SymbolRole.Definition) !== 0
330+
)
331+
)
332+
);
333+
}
334+
}
335+
} finally {
336+
fs.rmSync(projectRoot, { recursive: true, force: true });
337+
}
338+
}
339+
}
340+
277341
function testMissingProjectVersion(): void {
278342
// macOS temporary directories can be symlinked; match the analyzer's resolved file paths.
279343
const projectRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'scip-missing-version-')));
@@ -317,6 +381,7 @@ function testMissingProjectVersion(): void {
317381

318382
function unitTests(): void {
319383
testPyprojectParsing();
384+
testSourceSearchPaths();
320385
testMissingProjectVersion();
321386
pythonCLITests();
322387
}

0 commit comments

Comments
 (0)