From 603a9e982fd32ee55b1e6bf8b5ede4fd91db710e Mon Sep 17 00:00:00 2001 From: Varun Gandhi Date: Fri, 8 Sep 2023 17:19:35 +0800 Subject: [PATCH] feat: Detect name & version from pyproject.toml --- packages/pyright-scip/package-lock.json | 2 +- packages/pyright-scip/package.json | 2 +- .../snapshots/input/pyproject_1/main.py | 4 + .../input/pyproject_1/pyproject.toml | 5 + .../snapshots/output/pyproject_1/main.py | 13 +++ packages/pyright-scip/src/MainCommand.ts | 4 +- packages/pyright-scip/src/config.ts | 4 +- packages/pyright-scip/src/indexer.ts | 70 +++++++++-- packages/pyright-scip/test/test-main.ts | 110 ++++++++++++++++-- 9 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 packages/pyright-scip/snapshots/input/pyproject_1/main.py create mode 100644 packages/pyright-scip/snapshots/input/pyproject_1/pyproject.toml create mode 100644 packages/pyright-scip/snapshots/output/pyproject_1/main.py diff --git a/packages/pyright-scip/package-lock.json b/packages/pyright-scip/package-lock.json index eb3c55e42..e59be3274 100644 --- a/packages/pyright-scip/package-lock.json +++ b/packages/pyright-scip/package-lock.json @@ -9,7 +9,7 @@ "version": "0.4.2", "license": "MIT", "dependencies": { - "@iarna/toml": "2.2.5", + "@iarna/toml": "^2.2.5", "command-exists": "^1.2.9", "commander": "^9.2.0", "diff": "^5.0.0", diff --git a/packages/pyright-scip/package.json b/packages/pyright-scip/package.json index 6a5e1cf34..d9335fd2a 100644 --- a/packages/pyright-scip/package.json +++ b/packages/pyright-scip/package.json @@ -43,7 +43,7 @@ "webpack-cli": "^4.9.1" }, "dependencies": { - "@iarna/toml": "2.2.5", + "@iarna/toml": "^2.2.5", "command-exists": "^1.2.9", "commander": "^9.2.0", "diff": "^5.0.0", diff --git a/packages/pyright-scip/snapshots/input/pyproject_1/main.py b/packages/pyright-scip/snapshots/input/pyproject_1/main.py new file mode 100644 index 000000000..1cfbad29f --- /dev/null +++ b/packages/pyright-scip/snapshots/input/pyproject_1/main.py @@ -0,0 +1,4 @@ +def main(): + pass + +main() diff --git a/packages/pyright-scip/snapshots/input/pyproject_1/pyproject.toml b/packages/pyright-scip/snapshots/input/pyproject_1/pyproject.toml new file mode 100644 index 000000000..3e51bbfbc --- /dev/null +++ b/packages/pyright-scip/snapshots/input/pyproject_1/pyproject.toml @@ -0,0 +1,5 @@ +[project] +name = "abc" + +[tool.poetry] +version = "16.05" diff --git a/packages/pyright-scip/snapshots/output/pyproject_1/main.py b/packages/pyright-scip/snapshots/output/pyproject_1/main.py new file mode 100644 index 000000000..45eff829e --- /dev/null +++ b/packages/pyright-scip/snapshots/output/pyproject_1/main.py @@ -0,0 +1,13 @@ +# < definition scip-python python abc 16.05 main/__init__: +#documentation (module) main + +def main(): +# ^^^^ definition abc 16.05 main/main(). +# documentation ```python +# > def main(): # -> None: +# > ``` + pass + +main() +#^^^ reference abc 16.05 main/main(). + diff --git a/packages/pyright-scip/src/MainCommand.ts b/packages/pyright-scip/src/MainCommand.ts index 5bde7727d..a94e52687 100644 --- a/packages/pyright-scip/src/MainCommand.ts +++ b/packages/pyright-scip/src/MainCommand.ts @@ -87,8 +87,8 @@ export function mainCommand( .argument('', 'the directory containing `input` directories') .option('--check', 'whether to update or check', false) .option('--only ', 'only generate snapshots for ') - .requiredOption('--project-name ', 'the name of the current project, pypi name if applicable') - .requiredOption('--project-version ', 'the name of the current project, pypi name if applicable') + .option('--project-name ', 'the name of the current project, pypi name if applicable', '') + .option('--project-version ', 'the name of the current project, pypi name if applicable', '') .option( '--output ', 'Path to the output file. If this path is relative, it is interpreted relative to the value for --cwd.', diff --git a/packages/pyright-scip/src/config.ts b/packages/pyright-scip/src/config.ts index 9d21c96c6..6187dd14f 100644 --- a/packages/pyright-scip/src/config.ts +++ b/packages/pyright-scip/src/config.ts @@ -110,7 +110,7 @@ export class ScipPyrightConfig { pyprojectFilePath = this._findPyprojectTomlFile(projectRoot); if (!pyprojectFilePath && !commandLineOptions.fromVsCodeExtension) { - pyprojectFilePath = this._findPyprojectTomlFileHereOrUp(projectRoot); + pyprojectFilePath = this.findPyprojectTomlFileHereOrUp(projectRoot); } if (pyprojectFilePath) { @@ -364,7 +364,7 @@ export class ScipPyrightConfig { return undefined; } - private _findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined { + public findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined { return forEachAncestorDirectory(searchPath, (ancestor) => this._findPyprojectTomlFile(ancestor)); } diff --git a/packages/pyright-scip/src/indexer.ts b/packages/pyright-scip/src/indexer.ts index fa3e30402..db6840a3f 100644 --- a/packages/pyright-scip/src/indexer.ts +++ b/packages/pyright-scip/src/indexer.ts @@ -1,5 +1,6 @@ import * as child_process from 'child_process'; import * as path from 'path'; +import * as TOML from '@iarna/toml'; import { Event } from 'vscode-languageserver/lib/common/api'; import { Program } from 'pyright-internal/analyzer/program'; @@ -28,6 +29,45 @@ export class Indexer { pyrightConfig: ConfigOptions; projectFiles: Set; + public static inferProjectInfo( + inferProjectVersionFromCommit: boolean, + getPyprojectTomlContents: () => string | undefined + ): { name: string | undefined; version: string | undefined } { + let name = undefined; + let version = undefined; + try { + const pyprojectTomlContents = getPyprojectTomlContents(); + if (pyprojectTomlContents) { + const tomlMap = TOML.parse(pyprojectTomlContents); + // See: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#specification + let project = tomlMap['project'] as TOML.JsonMap | undefined; + if (project) { + name = project['name']; + version = project['version']; + } + if (!name || !version) { + // See: https://python-poetry.org/docs/pyproject/ + let tool = tomlMap['tool'] as TOML.JsonMap | undefined; + if (tool) { + let toolPoetry = tool['poetry'] as TOML.JsonMap | undefined; + if (toolPoetry) { + name = name ?? toolPoetry['name']; + version = version ?? toolPoetry['version']; + } + } + } + } + } catch (_) {} + name = typeof name === 'string' ? name : undefined; + version = typeof version === 'string' ? version : undefined; + if (!version && inferProjectVersionFromCommit) { + try { + version = child_process.execSync('git rev-parse HEAD').toString().trim(); + } catch (_) {} + } + return { name, version }; + } + constructor(public scipConfig: ScipConfig) { this.counter = new Counter(); @@ -40,23 +80,29 @@ export class Indexer { // // private _getConfigOptions(host: Host, commandLineOptions: CommandLineOptions): ConfigOptions { let fs = new PyrightFileSystem(createFromRealFileSystem()); + let config = new ScipPyrightConfig(scipConfig, fs); + this.pyrightConfig = config.getConfigOptions(); - if ( - scipConfig.infer.projectVersionFromCommit && - (!scipConfig.projectVersion || scipConfig.projectVersion === '') - ) { - try { - scipConfig.projectVersion = child_process.execSync('git rev-parse HEAD').toString().trim(); - } catch (e) { - scipConfig.projectVersion = ''; + if (!scipConfig.projectName || !scipConfig.projectVersion) { + const { name, version } = Indexer.inferProjectInfo( + scipConfig.infer.projectVersionFromCommit, + (): string | undefined => { + const tomlPath = config.findPyprojectTomlFileHereOrUp(scipConfig.projectRoot); + if (tomlPath) { + return fs.readFileSync(tomlPath, 'utf8'); + } + return undefined; + } + ); + if (!scipConfig.projectName && name) { + scipConfig.projectName = name; + } + if (!scipConfig.projectVersion && version) { + scipConfig.projectVersion = version; } } - let config = new ScipPyrightConfig(scipConfig, fs); - this.pyrightConfig = config.getConfigOptions(); - const matcher = new FileMatcher(this.pyrightConfig, fs); - this.projectFiles = new Set(matcher.matchFiles(this.pyrightConfig.include, this.pyrightConfig.exclude)); if (scipConfig.targetOnly) { scipConfig.targetOnly = path.resolve(scipConfig.targetOnly); diff --git a/packages/pyright-scip/test/test-main.ts b/packages/pyright-scip/test/test-main.ts index 56becd5a7..ad7054980 100644 --- a/packages/pyright-scip/test/test-main.ts +++ b/packages/pyright-scip/test/test-main.ts @@ -1,8 +1,97 @@ import { main } from '../src/main-impl'; import * as path from 'path'; import * as fs from 'fs'; +import { Indexer } from '../src/indexer'; -function testMain(mode: 'check' | 'update'): void { +function testPyprojectParsing() { + const testCases = [ + { + expected: { name: undefined, version: undefined }, + tomlContents: [ + ``, + `[project]`, + `[tool.poetry]`, + `[tool] +poetry = {}`, + `[tool.poetry] +name = false +version = {}`, + ], + }, + { + expected: { name: 'abc', version: undefined }, + tomlContents: [ + `[project] +name = "abc"`, + `[tool.poetry] +name = "abc"`, + `[tool] +poetry = { name = "abc" }`, + `[project] +name = "abc" +[tool.poetry] +name = "ignored"`, + ], + }, + { + expected: { name: undefined, version: '16.05' }, + tomlContents: [ + `[project] +version = "16.05"`, + `[tool.poetry] +version = "16.05"`, + `[tool] +poetry = { version = "16.05" }`, + `[project] +version = "16.05" +[tool.poetry] +version = "ignored"`, + ], + }, + { + expected: { name: 'abc', version: '16.05' }, + tomlContents: [ + `[project] +name = "abc" +version = "16.05"`, + `[tool.poetry] +name = "abc" +version = "16.05"`, + `[project] +name = "abc" +[tool.poetry] +version = "16.05"`, + `[project] +version = "16.05" +[tool.poetry] +name = "abc"`, + `[project] +[tool.poetry] +name = "abc" +version = "16.05"`, + ], + }, + ]; + + for (const testCase of testCases) { + for (const content of testCase.tomlContents) { + const got = Indexer.inferProjectInfo(false, () => content); + const want = testCase.expected; + if (got.name !== want.name) { + throw `name mismatch (got: ${got.name}, expected: ${want.name}) for ${content}`; + } + if (got.version !== want.version) { + throw `version mismatch (got: ${got.version}, expected: ${want.version}) for ${content}`; + } + } + } +} + +function unitTests(): void { + testPyprojectParsing(); +} + +function snapshotTests(mode: 'check' | 'update'): void { const nodePath = process.argv[0]; const startCwd = process.cwd(); // Returns list of subdir names, not absolute paths. @@ -12,8 +101,12 @@ function testMain(mode: 'check' | 'update'): void { const packageInfo = JSON.parse(fs.readFileSync(packageInfoPath, 'utf8')); for (const subdirName of subdirNames) { console.assert(!subdirName.includes(path.sep)); - let projectName = packageInfo['default']['name']; - let projectVersion = packageInfo['default']['version']; + let projectName = undefined; + let projectVersion = undefined; + if (!fs.existsSync(path.join(inputDir, subdirName, 'pyproject.toml'))) { + projectName = packageInfo['default']['name']; + projectVersion = packageInfo['default']['version']; + } if (subdirName in packageInfo['special']) { projectName = packageInfo['special'][subdirName]['name']; projectVersion = packageInfo['special'][subdirName]['version']; @@ -26,13 +119,11 @@ function testMain(mode: 'check' | 'update'): void { '--environment', 'snapshots/testEnv.json', '--quiet', - '--project-name', - projectName, - '--project-version', - projectVersion, '--only', subdirName, ]; // FIXME: This should pass with a --dev flag + argv.push(...(projectName ? ['--project-name', projectName] : [])); + argv.push(...(projectVersion ? ['--project-version', projectVersion] : [])); if (mode === 'check') { argv.push('--check'); } @@ -40,6 +131,11 @@ function testMain(mode: 'check' | 'update'): void { } } +function testMain(mode: 'check' | 'update'): void { + unitTests(); + snapshotTests(mode); +} + if (process.argv.indexOf('--check') !== -1) { testMain('check'); } else {