Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions build/snap_01_add_ids.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--- .build/snapcraft_upstream.json.orig 2025-10-31 14:39:20.642999620 +0100
+++ src/craft_ls/schemas/snapcraft.json 2025-10-31 15:10:13.397566863 +0100
@@ -1,4 +1,5 @@
{
+ "$id": "#snapcraft",
"$defs": {
"App": {
"additionalProperties": false,
@@ -503,6 +504,7 @@
"type": "object"
},
"BareCore22Project": {
+ "$id": "urn:snapcraft:bare22",
"additionalProperties": false,
"properties": {
"name": {
@@ -1090,6 +1092,7 @@
"type": "object"
},
"BareCore24Project": {
+ "$id": "urn:snapcraft:bare24",
"additionalProperties": false,
"properties": {
"name": {
@@ -1678,6 +1681,7 @@
"type": "object"
},
"BaseCore22Project": {
+ "$id": "urn:snapcraft:base22",
"additionalProperties": false,
"properties": {
"name": {
@@ -2259,6 +2263,7 @@
"type": "object"
},
"BaseCore24Project": {
+ "$id": "urn:snapcraft:base24",
"additionalProperties": false,
"properties": {
"name": {
@@ -2840,6 +2845,7 @@
"type": "object"
},
"BaseDevelProject": {
+ "$id": "urn:snapcraft:devel",
"additionalProperties": false,
"properties": {
"name": {
@@ -3555,6 +3561,7 @@
"type": "object"
},
"Core22Project": {
+ "$id": "urn:snapcraft:core22",
"additionalProperties": false,
"properties": {
"name": {
@@ -4151,6 +4158,7 @@
"type": "object"
},
"Core24Project": {
+ "$id": "urn:snapcraft:core24",
"additionalProperties": false,
"properties": {
"name": {
20 changes: 0 additions & 20 deletions build/snap_01_build_err.patch

This file was deleted.

17 changes: 2 additions & 15 deletions src/craft_ls/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from lsprotocol import types as lsp

from craft_ls.core import get_diagnostics, get_validator_and_scan
from craft_ls.types_ import IncompleteScan

logging.basicConfig()

Expand All @@ -21,20 +20,8 @@ def check(file_name: str) -> None:

diagnostics: list[lsp.Diagnostic] = []
match get_validator_and_scan(file.stem, file.read_text()):
case None, _:
pass

case None, IncompleteScan():
diagnostics.append(
lsp.Diagnostic(
message="File is malformed",
range=lsp.Range(
start=lsp.Position(line=0, character=0),
end=lsp.Position(line=0, character=0),
),
severity=lsp.DiagnosticSeverity.Warning,
)
)
case None:
print(f"Cannot validate '{file}'", file=sys.stderr)
pass

case validator, scan_result:
Expand Down
110 changes: 87 additions & 23 deletions src/craft_ls/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,19 @@
files("craft_ls.schemas").joinpath(f"{file_type}.json").read_text()
)
default_validators[file_type] = validator_for(schema)(schema)

if file_type == "charmcraft":
schema = Resource.from_contents(
jsonref.loads(schema_str), default_specification=DRAFT202012
)
charmcraft_registry = schema @ Registry()

if file_type == "snapcraft":
schema = Resource.from_contents(
jsonref.loads(schema_str), default_specification=DRAFT202012
)
snapcraft_registry = schema @ Registry()


class MissingTypeCharmcraftValidator:
"""No op implementation.
Expand All @@ -87,16 +94,91 @@ def iter_errors(
)


def get_validator_and_scan(
class MissingTypeSnapcraftValidator:
"""No op implementation.

Used if snapcraft.yaml is missing the 'base' or 'build-base' key.
"""

def iter_errors(
self, instance: Any, _schema: Any = None
) -> Generator[ValidationError, None, None]:
"""Lazily yield each of the validation errors in the given instance."""
yield ValidationError(
validator="required",
path=deque([]),
message="Filling 'base' and/or 'build-base' key(s) is mandatory.",
schema={},
)


def get_validator_and_scan( # noqa: C901
file_stem: str, instance_document: str
) -> tuple[Validator | None, ScanResult]:
) -> tuple[Validator, ScanResult] | None:
"""Get the most appropriate validator for the current document."""
if file_stem not in FILE_TYPES:
return None

scanned_tokens = scan_for_tokens(instance_document)

if file_stem in ("snapcraft", "rockcraft"):
if file_stem == "rockcraft":
return default_validators[file_stem], scanned_tokens

if file_stem == "charmcraft":
elif file_stem == "snapcraft":
base = scanned_tokens.instance.get("base", None)
build_base = scanned_tokens.instance.get("build-base", None)
validator: Draft202012Validator | MissingTypeSnapcraftValidator
match base, build_base:
case "core22", _:
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:core22")
.contents
)
case "core24", _:
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:core24")
.contents
)
case "bare", "core22":
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:bare22")
.contents
)
case "bare", "core24":
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:bare24")
.contents
)
case _, "core22":
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:base22")
.contents
)
case _, "core24":
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:base24")
.contents
)
case _, "devel":
validator = Draft202012Validator(
schema=snapcraft_registry.resolver()
.lookup("urn:snapcraft:devel")
.contents
)

case _:
validator = MissingTypeSnapcraftValidator()

return cast(Validator, validator), scanned_tokens

else:
# by elimination, file_stem is charmcraft
if scanned_tokens.instance.get("type") != "charm":
return cast(Validator, MissingTypeCharmcraftValidator()), scanned_tokens

Expand All @@ -105,8 +187,7 @@ def get_validator_and_scan(
.lookup("urn:charmcraft:platformcharm")
.contents
)
return cast(Validator, validator), scanned_tokens
return None, scanned_tokens
return cast(Validator, validator), scanned_tokens


def scan_for_tokens(instance_document: str) -> ScanResult:
Expand Down Expand Up @@ -322,23 +403,6 @@ def get_description_from_path(path: Iterable[str | int], schema: Schema) -> str:
return MISSING_DESC


def get_description_from_path_snapcraft(
path: Iterable[str | int], schema: Schema
) -> str:
"""Given an element path, get its description.

Limited in capability, as snapcraft schema used patterned properties.
"""
sub = schema
for segment in path:
if "patternProperties" in sub:
sub = next(iter(sub["patternProperties"].values()), cast(Schema, {}))
continue
sub = sub.get("properties", {}).get(segment, {})

return str(sub.get("description", sub.get("title", MISSING_DESC))).capitalize()


def get_schema_path_from_token_position(
position: lsp.Position, instance_document: str
) -> deque[str] | None:
Expand Down
68 changes: 16 additions & 52 deletions src/craft_ls/schemas/charmcraft.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@
"default": null,
"title": "Title"
},
"version": {
"const": "unversioned",
"default": "unversioned",
"title": "Version",
"type": "string"
},
"summary": {
"description": "A brief (one-line) summary of your charm.",
"maxLength": 200,
Expand Down Expand Up @@ -120,26 +114,6 @@
"default": null,
"title": "Build-Base"
},
"contact": {
"default": null,
"title": "Contact",
"type": "null"
},
"issues": {
"default": null,
"title": "Issues",
"type": "null"
},
"source-code": {
"default": null,
"title": "Source-Code",
"type": "null"
},
"license": {
"default": null,
"title": "License",
"type": "null"
},
"adopt-info": {
"anyOf": [
{
Expand All @@ -150,6 +124,10 @@
}
],
"default": null,
"description": "Selects a part to inherit metadata from.",
"examples": [
"foo-part"
],
"title": "Adopt-Info"
},
"parts": {
Expand Down Expand Up @@ -181,6 +159,10 @@
}
],
"default": null,
"description": "The package repositories to use for build and stage packages.",
"examples": [
"[{type: apt, components: [main], suites: [xenial], key-id: 78E1918602959B9C59103100F1831DDAFC42E99D, url: http://ppa.launchpad.net/snappy-dev/snapcraft-daily/ubuntu}]"
],
"title": "Package-Repositories"
},
"type": {
Expand Down Expand Up @@ -901,12 +883,6 @@
"default": null,
"title": "Title"
},
"version": {
"const": "unversioned",
"default": "unversioned",
"title": "Version",
"type": "string"
},
"summary": {
"description": "A brief (one-line) summary of your charm.",
"maxLength": 200,
Expand Down Expand Up @@ -1018,26 +994,6 @@
"title": "Platforms",
"type": "object"
},
"contact": {
"default": null,
"title": "Contact",
"type": "null"
},
"issues": {
"default": null,
"title": "Issues",
"type": "null"
},
"source-code": {
"default": null,
"title": "Source-Code",
"type": "null"
},
"license": {
"default": null,
"title": "License",
"type": "null"
},
"adopt-info": {
"anyOf": [
{
Expand All @@ -1048,6 +1004,10 @@
}
],
"default": null,
"description": "Selects a part to inherit metadata from.",
"examples": [
"foo-part"
],
"title": "Adopt-Info"
},
"parts": {
Expand All @@ -1074,6 +1034,10 @@
}
],
"default": null,
"description": "The package repositories to use for build and stage packages.",
"examples": [
"[{type: apt, components: [main], suites: [xenial], key-id: 78E1918602959B9C59103100F1831DDAFC42E99D, url: http://ppa.launchpad.net/snappy-dev/snapcraft-daily/ubuntu}]"
],
"title": "Package-Repositories"
},
"type": {
Expand Down
Loading
Loading