diff --git a/src/poetry/core/factory.py b/src/poetry/core/factory.py index 1d56d78c7..0a776659c 100644 --- a/src/poetry/core/factory.py +++ b/src/poetry/core/factory.py @@ -409,6 +409,22 @@ def validate( 'Use "allow-prereleases" instead.' ) + if "extras" in config: + for extra_name, requirements in config["extras"].items(): + extra_name = canonicalize_name(extra_name) + + for req in requirements: + req_name = canonicalize_name(req) + for dependency in config.get("dependencies", {}).keys(): + dep_name = canonicalize_name(dependency) + if req_name == dep_name: + break + else: + result["errors"].append( + f'Cannot find dependency "{req}" for extra ' + f'"{extra_name}" in main dependencies.' + ) + # Checking for scripts with extras if "scripts" in config: scripts = config["scripts"] diff --git a/src/poetry/core/json/schemas/poetry-schema.json b/src/poetry/core/json/schemas/poetry-schema.json index a3a09738c..9b39a1047 100644 --- a/src/poetry/core/json/schemas/poetry-schema.json +++ b/src/poetry/core/json/schemas/poetry-schema.json @@ -155,7 +155,8 @@ "^[a-zA-Z-_.0-9]+$": { "type": "array", "items": { - "type": "string" + "type": "string", + "pattern": "^[a-zA-Z-_.0-9]+$" } } } diff --git a/tests/fixtures/project_failing_strict_validation/pyproject.toml b/tests/fixtures/project_failing_strict_validation/pyproject.toml index 6d282ba97..3fc8d4fbb 100644 --- a/tests/fixtures/project_failing_strict_validation/pyproject.toml +++ b/tests/fixtures/project_failing_strict_validation/pyproject.toml @@ -5,6 +5,9 @@ readme = ["README.rst", "README_WITH_ANOTHER_EXTENSION.md"] python = "*" pathlib2 = { version = "^2.2", python = "3.7", allows-prereleases = true } +[tool.poetry.extras] +some_extras = ["missing_extra", "another_missing_extra"] + [tool.poetry.scripts] a_script_with_unknown_extra = { reference = "a_script_with_unknown_extra.py", type = "file", extras = ["foo"] } a_script_without_extras = { reference = "a_script_without_extras.py", type = "file" } diff --git a/tests/json/test_poetry_schema.py b/tests/json/test_poetry_schema.py index bb6399abb..b2409b56d 100644 --- a/tests/json/test_poetry_schema.py +++ b/tests/json/test_poetry_schema.py @@ -65,3 +65,15 @@ def test_multiline_description( regex = r"\\A[^\n]*\\Z" assert errors[0] == f"[description] {bad_description!r} does not match '{regex}'" + + +def test_bad_extra(base_object: dict[str, Any]) -> None: + bad_extra = "a{[*+" + base_object["extras"] = {} + base_object["extras"]["test"] = [bad_extra] + + errors = validate_object(base_object, "poetry-schema") + assert len(errors) == 1 + assert ( + errors[0] == f"[extras.test.0] {bad_extra!r} does not match '^[a-zA-Z-_.0-9]+$'" + ) diff --git a/tests/test_factory.py b/tests/test_factory.py index af4101dd0..1a5f23091 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -232,6 +232,14 @@ def test_validate_strict_fails_strict_and_non_strict() -> None: "'version' is a required property", "'description' is a required property", "'authors' is a required property", + ( + 'Cannot find dependency "missing_extra" for extra "some-extras" in ' + "main dependencies." + ), + ( + 'Cannot find dependency "another_missing_extra" for extra ' + '"some-extras" in main dependencies.' + ), ( 'Script "a_script_with_unknown_extra" requires extra "foo" which is not' " defined."