Skip to content

Commit 556e771

Browse files
feat: Detect name & version from pyproject.toml (sourcegraph#121)
1 parent 029e3d8 commit 556e771

File tree

9 files changed

+189
-25
lines changed

9 files changed

+189
-25
lines changed

packages/pyright-scip/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/pyright-scip/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"webpack-cli": "^4.9.1"
4444
},
4545
"dependencies": {
46-
"@iarna/toml": "2.2.5",
46+
"@iarna/toml": "^2.2.5",
4747
"command-exists": "^1.2.9",
4848
"commander": "^9.2.0",
4949
"diff": "^5.0.0",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def main():
2+
pass
3+
4+
main()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[project]
2+
name = "abc"
3+
4+
[tool.poetry]
5+
version = "16.05"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# < definition scip-python python abc 16.05 main/__init__:
2+
#documentation (module) main
3+
4+
def main():
5+
# ^^^^ definition abc 16.05 main/main().
6+
# documentation ```python
7+
# > def main(): # -> None:
8+
# > ```
9+
pass
10+
11+
main()
12+
#^^^ reference abc 16.05 main/main().
13+

packages/pyright-scip/src/MainCommand.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ export function mainCommand(
8787
.argument('<path>', 'the directory containing `input` directories')
8888
.option('--check', 'whether to update or check', false)
8989
.option('--only <name>', 'only generate snapshots for <name>')
90-
.requiredOption('--project-name <name>', 'the name of the current project, pypi name if applicable')
91-
.requiredOption('--project-version <version>', 'the name of the current project, pypi name if applicable')
90+
.option('--project-name <name>', 'the name of the current project, pypi name if applicable', '')
91+
.option('--project-version <version>', 'the name of the current project, pypi name if applicable', '')
9292
.option(
9393
'--output <path>',
9494
'Path to the output file. If this path is relative, it is interpreted relative to the value for --cwd.',

packages/pyright-scip/src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export class ScipPyrightConfig {
110110
pyprojectFilePath = this._findPyprojectTomlFile(projectRoot);
111111

112112
if (!pyprojectFilePath && !commandLineOptions.fromVsCodeExtension) {
113-
pyprojectFilePath = this._findPyprojectTomlFileHereOrUp(projectRoot);
113+
pyprojectFilePath = this.findPyprojectTomlFileHereOrUp(projectRoot);
114114
}
115115

116116
if (pyprojectFilePath) {
@@ -364,7 +364,7 @@ export class ScipPyrightConfig {
364364
return undefined;
365365
}
366366

367-
private _findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined {
367+
public findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined {
368368
return forEachAncestorDirectory(searchPath, (ancestor) => this._findPyprojectTomlFile(ancestor));
369369
}
370370

packages/pyright-scip/src/indexer.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as child_process from 'child_process';
22
import * as path from 'path';
3+
import * as TOML from '@iarna/toml';
34
import { Event } from 'vscode-languageserver/lib/common/api';
45

56
import { Program } from 'pyright-internal/analyzer/program';
@@ -28,6 +29,45 @@ export class Indexer {
2829
pyrightConfig: ConfigOptions;
2930
projectFiles: Set<string>;
3031

32+
public static inferProjectInfo(
33+
inferProjectVersionFromCommit: boolean,
34+
getPyprojectTomlContents: () => string | undefined
35+
): { name: string | undefined; version: string | undefined } {
36+
let name = undefined;
37+
let version = undefined;
38+
try {
39+
const pyprojectTomlContents = getPyprojectTomlContents();
40+
if (pyprojectTomlContents) {
41+
const tomlMap = TOML.parse(pyprojectTomlContents);
42+
// See: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#specification
43+
let project = tomlMap['project'] as TOML.JsonMap | undefined;
44+
if (project) {
45+
name = project['name'];
46+
version = project['version'];
47+
}
48+
if (!name || !version) {
49+
// See: https://python-poetry.org/docs/pyproject/
50+
let tool = tomlMap['tool'] as TOML.JsonMap | undefined;
51+
if (tool) {
52+
let toolPoetry = tool['poetry'] as TOML.JsonMap | undefined;
53+
if (toolPoetry) {
54+
name = name ?? toolPoetry['name'];
55+
version = version ?? toolPoetry['version'];
56+
}
57+
}
58+
}
59+
}
60+
} catch (_) {}
61+
name = typeof name === 'string' ? name : undefined;
62+
version = typeof version === 'string' ? version : undefined;
63+
if (!version && inferProjectVersionFromCommit) {
64+
try {
65+
version = child_process.execSync('git rev-parse HEAD').toString().trim();
66+
} catch (_) {}
67+
}
68+
return { name, version };
69+
}
70+
3171
constructor(public scipConfig: ScipConfig) {
3272
this.counter = new Counter();
3373

@@ -40,23 +80,29 @@ export class Indexer {
4080
//
4181
// private _getConfigOptions(host: Host, commandLineOptions: CommandLineOptions): ConfigOptions {
4282
let fs = new PyrightFileSystem(createFromRealFileSystem());
83+
let config = new ScipPyrightConfig(scipConfig, fs);
84+
this.pyrightConfig = config.getConfigOptions();
4385

44-
if (
45-
scipConfig.infer.projectVersionFromCommit &&
46-
(!scipConfig.projectVersion || scipConfig.projectVersion === '')
47-
) {
48-
try {
49-
scipConfig.projectVersion = child_process.execSync('git rev-parse HEAD').toString().trim();
50-
} catch (e) {
51-
scipConfig.projectVersion = '';
86+
if (!scipConfig.projectName || !scipConfig.projectVersion) {
87+
const { name, version } = Indexer.inferProjectInfo(
88+
scipConfig.infer.projectVersionFromCommit,
89+
(): string | undefined => {
90+
const tomlPath = config.findPyprojectTomlFileHereOrUp(scipConfig.projectRoot);
91+
if (tomlPath) {
92+
return fs.readFileSync(tomlPath, 'utf8');
93+
}
94+
return undefined;
95+
}
96+
);
97+
if (!scipConfig.projectName && name) {
98+
scipConfig.projectName = name;
99+
}
100+
if (!scipConfig.projectVersion && version) {
101+
scipConfig.projectVersion = version;
52102
}
53103
}
54104

55-
let config = new ScipPyrightConfig(scipConfig, fs);
56-
this.pyrightConfig = config.getConfigOptions();
57-
58105
const matcher = new FileMatcher(this.pyrightConfig, fs);
59-
60106
this.projectFiles = new Set(matcher.matchFiles(this.pyrightConfig.include, this.pyrightConfig.exclude));
61107
if (scipConfig.targetOnly) {
62108
scipConfig.targetOnly = path.resolve(scipConfig.targetOnly);

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

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,97 @@
11
import { main } from '../src/main-impl';
22
import * as path from 'path';
33
import * as fs from 'fs';
4+
import { Indexer } from '../src/indexer';
45

5-
function testMain(mode: 'check' | 'update'): void {
6+
function testPyprojectParsing() {
7+
const testCases = [
8+
{
9+
expected: { name: undefined, version: undefined },
10+
tomlContents: [
11+
``,
12+
`[project]`,
13+
`[tool.poetry]`,
14+
`[tool]
15+
poetry = {}`,
16+
`[tool.poetry]
17+
name = false
18+
version = {}`,
19+
],
20+
},
21+
{
22+
expected: { name: 'abc', version: undefined },
23+
tomlContents: [
24+
`[project]
25+
name = "abc"`,
26+
`[tool.poetry]
27+
name = "abc"`,
28+
`[tool]
29+
poetry = { name = "abc" }`,
30+
`[project]
31+
name = "abc"
32+
[tool.poetry]
33+
name = "ignored"`,
34+
],
35+
},
36+
{
37+
expected: { name: undefined, version: '16.05' },
38+
tomlContents: [
39+
`[project]
40+
version = "16.05"`,
41+
`[tool.poetry]
42+
version = "16.05"`,
43+
`[tool]
44+
poetry = { version = "16.05" }`,
45+
`[project]
46+
version = "16.05"
47+
[tool.poetry]
48+
version = "ignored"`,
49+
],
50+
},
51+
{
52+
expected: { name: 'abc', version: '16.05' },
53+
tomlContents: [
54+
`[project]
55+
name = "abc"
56+
version = "16.05"`,
57+
`[tool.poetry]
58+
name = "abc"
59+
version = "16.05"`,
60+
`[project]
61+
name = "abc"
62+
[tool.poetry]
63+
version = "16.05"`,
64+
`[project]
65+
version = "16.05"
66+
[tool.poetry]
67+
name = "abc"`,
68+
`[project]
69+
[tool.poetry]
70+
name = "abc"
71+
version = "16.05"`,
72+
],
73+
},
74+
];
75+
76+
for (const testCase of testCases) {
77+
for (const content of testCase.tomlContents) {
78+
const got = Indexer.inferProjectInfo(false, () => content);
79+
const want = testCase.expected;
80+
if (got.name !== want.name) {
81+
throw `name mismatch (got: ${got.name}, expected: ${want.name}) for ${content}`;
82+
}
83+
if (got.version !== want.version) {
84+
throw `version mismatch (got: ${got.version}, expected: ${want.version}) for ${content}`;
85+
}
86+
}
87+
}
88+
}
89+
90+
function unitTests(): void {
91+
testPyprojectParsing();
92+
}
93+
94+
function snapshotTests(mode: 'check' | 'update'): void {
695
const nodePath = process.argv[0];
796
const startCwd = process.cwd();
897
// Returns list of subdir names, not absolute paths.
@@ -12,8 +101,12 @@ function testMain(mode: 'check' | 'update'): void {
12101
const packageInfo = JSON.parse(fs.readFileSync(packageInfoPath, 'utf8'));
13102
for (const subdirName of subdirNames) {
14103
console.assert(!subdirName.includes(path.sep));
15-
let projectName = packageInfo['default']['name'];
16-
let projectVersion = packageInfo['default']['version'];
104+
let projectName = undefined;
105+
let projectVersion = undefined;
106+
if (!fs.existsSync(path.join(inputDir, subdirName, 'pyproject.toml'))) {
107+
projectName = packageInfo['default']['name'];
108+
projectVersion = packageInfo['default']['version'];
109+
}
17110
if (subdirName in packageInfo['special']) {
18111
projectName = packageInfo['special'][subdirName]['name'];
19112
projectVersion = packageInfo['special'][subdirName]['version'];
@@ -26,20 +119,23 @@ function testMain(mode: 'check' | 'update'): void {
26119
'--environment',
27120
'snapshots/testEnv.json',
28121
'--quiet',
29-
'--project-name',
30-
projectName,
31-
'--project-version',
32-
projectVersion,
33122
'--only',
34123
subdirName,
35124
]; // FIXME: This should pass with a --dev flag
125+
argv.push(...(projectName ? ['--project-name', projectName] : []));
126+
argv.push(...(projectVersion ? ['--project-version', projectVersion] : []));
36127
if (mode === 'check') {
37128
argv.push('--check');
38129
}
39130
main(argv);
40131
}
41132
}
42133

134+
function testMain(mode: 'check' | 'update'): void {
135+
unitTests();
136+
snapshotTests(mode);
137+
}
138+
43139
if (process.argv.indexOf('--check') !== -1) {
44140
testMain('check');
45141
} else {

0 commit comments

Comments
 (0)