mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-06 16:19:07 -04:00
Compare commits
8 commits
0f417ec6f4
...
dc6fe66d32
Author | SHA1 | Date | |
---|---|---|---|
|
dc6fe66d32 | ||
|
778a979714 | ||
|
6acf0fe5a0 | ||
|
077610b877 | ||
|
9429ac5f41 | ||
|
ca4ef1db88 | ||
|
c5949de543 | ||
|
f2d4643bf5 |
7 changed files with 185 additions and 36 deletions
|
@ -25,7 +25,8 @@ from functools import cached_property
|
|||
import gi # type: ignore
|
||||
|
||||
gi.require_version("GIRepository", "2.0")
|
||||
from gi.repository import GIRepository # type: ignore
|
||||
gi.require_version("GLib", "2.0")
|
||||
from gi.repository import GIRepository, GLib # type: ignore
|
||||
|
||||
from . import typelib, xml_reader
|
||||
from .errors import CompileError, CompilerBugError
|
||||
|
@ -35,19 +36,60 @@ _namespace_cache: T.Dict[str, "Namespace"] = {}
|
|||
_xml_cache = {}
|
||||
|
||||
_user_search_paths = []
|
||||
_typelib_search_paths_initalized: bool = False
|
||||
_typelib_search_paths: list[str] = []
|
||||
_gir_search_paths_initalized: bool = False
|
||||
_gir_search_paths: list[str] = []
|
||||
|
||||
|
||||
def add_typelib_search_path(path: str):
|
||||
_user_search_paths.append(path)
|
||||
def add_user_typelib_paths(paths: list):
|
||||
global _user_search_paths
|
||||
_user_search_paths += paths
|
||||
|
||||
|
||||
def collect_typelib_search_paths():
|
||||
global _typelib_search_paths_initalized
|
||||
if _typelib_search_paths_initalized:
|
||||
return
|
||||
|
||||
global _typelib_search_paths
|
||||
_typelib_search_paths += [
|
||||
*_user_search_paths,
|
||||
*GIRepository.Repository.get_search_path(),
|
||||
# fallback paths
|
||||
"/usr/local/lib/girepository-1.0",
|
||||
"/usr/local/lib64/girepository-1.0",
|
||||
"/usr/lib/girepository-1.0",
|
||||
"/usr/lib64/girepository-1.0",
|
||||
]
|
||||
_typelib_search_paths_initalized = True
|
||||
|
||||
|
||||
def collect_gir_search_paths():
|
||||
global _gir_search_paths_initalized
|
||||
if _gir_search_paths_initalized:
|
||||
return
|
||||
|
||||
base_paths = [
|
||||
GLib.get_user_data_dir(),
|
||||
*GLib.get_system_data_dirs(),
|
||||
# fallback paths
|
||||
"/usr/local/share/",
|
||||
"/usr/share/",
|
||||
]
|
||||
|
||||
global _gir_search_paths
|
||||
_gir_search_paths = [os.path.join(path, "gir-1.0") for path in base_paths]
|
||||
_gir_search_paths_initalized = True
|
||||
|
||||
|
||||
def get_namespace(namespace: str, version: str) -> "Namespace":
|
||||
search_paths = [*GIRepository.Repository.get_search_path(), *_user_search_paths]
|
||||
collect_typelib_search_paths()
|
||||
|
||||
filename = f"{namespace}-{version}.typelib"
|
||||
|
||||
if filename not in _namespace_cache:
|
||||
for search_path in search_paths:
|
||||
for search_path in _typelib_search_paths:
|
||||
path = os.path.join(search_path, filename)
|
||||
|
||||
if os.path.exists(path) and os.path.isfile(path):
|
||||
|
@ -60,7 +102,7 @@ def get_namespace(namespace: str, version: str) -> "Namespace":
|
|||
if filename not in _namespace_cache:
|
||||
raise CompileError(
|
||||
f"Namespace {namespace}-{version} could not be found",
|
||||
hints=["search path: " + os.pathsep.join(search_paths)],
|
||||
hints=["search path: " + os.pathsep.join(_typelib_search_paths)],
|
||||
)
|
||||
|
||||
return _namespace_cache[filename]
|
||||
|
@ -73,12 +115,8 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
|||
if len(_available_namespaces):
|
||||
return _available_namespaces
|
||||
|
||||
search_paths: list[str] = [
|
||||
*GIRepository.Repository.get_search_path(),
|
||||
*_user_search_paths,
|
||||
]
|
||||
|
||||
for search_path in search_paths:
|
||||
collect_typelib_search_paths()
|
||||
for search_path in _typelib_search_paths:
|
||||
try:
|
||||
filenames = os.listdir(search_path)
|
||||
except FileNotFoundError:
|
||||
|
@ -93,17 +131,12 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
|||
|
||||
|
||||
def get_xml(namespace: str, version: str):
|
||||
search_paths = []
|
||||
|
||||
if data_paths := os.environ.get("XDG_DATA_DIRS"):
|
||||
search_paths += [
|
||||
os.path.join(path, "gir-1.0") for path in data_paths.split(os.pathsep)
|
||||
]
|
||||
collect_gir_search_paths()
|
||||
|
||||
filename = f"{namespace}-{version}.gir"
|
||||
|
||||
if filename not in _xml_cache:
|
||||
for search_path in search_paths:
|
||||
for search_path in _gir_search_paths:
|
||||
path = os.path.join(search_path, filename)
|
||||
|
||||
if os.path.exists(path) and os.path.isfile(path):
|
||||
|
@ -113,7 +146,7 @@ def get_xml(namespace: str, version: str):
|
|||
if filename not in _xml_cache:
|
||||
raise CompileError(
|
||||
f"GObject introspection file '{namespace}-{version}.gir' could not be found",
|
||||
hints=["search path: " + os.pathsep.join(search_paths)],
|
||||
hints=["search path: " + os.pathsep.join(_gir_search_paths)],
|
||||
)
|
||||
|
||||
return _xml_cache[filename]
|
||||
|
|
|
@ -171,6 +171,24 @@ class LookupOp(InfixExpr):
|
|||
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
|
||||
)
|
||||
|
||||
@validate("property")
|
||||
def property_deprecated(self):
|
||||
if self.lhs.type is None or not (
|
||||
isinstance(self.lhs.type, gir.Class)
|
||||
or isinstance(self.lhs.type, gir.Interface)
|
||||
):
|
||||
return
|
||||
|
||||
if property := self.lhs.type.properties.get(self.property_name):
|
||||
if property.deprecated:
|
||||
hints = []
|
||||
if property.deprecated_doc:
|
||||
hints.append(property.deprecated_doc)
|
||||
raise DeprecatedWarning(
|
||||
f"{property.signature} is deprecated",
|
||||
hints=hints,
|
||||
)
|
||||
|
||||
|
||||
class CastExpr(InfixExpr):
|
||||
grammar = [
|
||||
|
|
|
@ -149,7 +149,7 @@ class LanguageServer:
|
|||
|
||||
def _send(self, data):
|
||||
data["jsonrpc"] = "2.0"
|
||||
line = json.dumps(data, separators=(",", ":")) + "\r\n"
|
||||
line = json.dumps(data, separators=(",", ":"))
|
||||
printerr("output: " + line)
|
||||
sys.stdout.write(
|
||||
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
|
||||
|
|
|
@ -27,7 +27,7 @@ import typing as T
|
|||
from . import formatter, interactive_port, parser, tokenizer
|
||||
from .decompiler import decompile_string
|
||||
from .errors import CompileError, CompilerBugError, PrintableError, report_bug
|
||||
from .gir import add_typelib_search_path
|
||||
from .gir import add_user_typelib_paths
|
||||
from .lsp import LanguageServer
|
||||
from .outputs import XmlOutput
|
||||
from .utils import Colors
|
||||
|
@ -145,8 +145,7 @@ class BlueprintApp:
|
|||
|
||||
def cmd_compile(self, opts):
|
||||
if opts.typelib_path != None:
|
||||
for typelib_path in opts.typelib_path:
|
||||
add_typelib_search_path(typelib_path)
|
||||
add_user_typelib_paths(opts.typelib_path)
|
||||
|
||||
data = opts.input.read()
|
||||
try:
|
||||
|
@ -166,8 +165,7 @@ class BlueprintApp:
|
|||
|
||||
def cmd_batch_compile(self, opts):
|
||||
if opts.typelib_path != None:
|
||||
for typelib_path in opts.typelib_path:
|
||||
add_typelib_search_path(typelib_path)
|
||||
add_user_typelib_paths(opts.typelib_path)
|
||||
|
||||
for file in opts.inputs:
|
||||
data = file.read()
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Dialog {
|
||||
use-header-bar: 1;
|
||||
}
|
||||
|
||||
Window {
|
||||
keys-changed => $on_window_keys_changed();
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
3,1,6,Gtk.Dialog is deprecated
|
110
tests/test_deprecations.py
Normal file
110
tests/test_deprecations.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
# test_samples.py
|
||||
#
|
||||
# Copyright 2024 James Westman <james@jwestman.net>
|
||||
#
|
||||
# This file is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "4.0")
|
||||
from gi.repository import Gtk
|
||||
|
||||
from blueprintcompiler import parser, tokenizer
|
||||
from blueprintcompiler.errors import DeprecatedWarning, PrintableError
|
||||
|
||||
# Testing deprecation warnings requires special handling because libraries can add deprecations with new versions,
|
||||
# causing tests to break if we're not careful.
|
||||
|
||||
|
||||
class TestDeprecations(unittest.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.gtkVersion = f"{Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}"
|
||||
|
||||
def assertDeprecation(self, blueprint: str, message: str):
|
||||
try:
|
||||
tokens = tokenizer.tokenize(blueprint)
|
||||
_ast, errors, warnings = parser.parse(tokens)
|
||||
|
||||
self.assertIsNone(errors)
|
||||
self.assertEqual(len(warnings), 1)
|
||||
self.assertIsInstance(warnings[0], DeprecatedWarning)
|
||||
self.assertEqual(warnings[0].message, message)
|
||||
except PrintableError as e: # pragma: no cover
|
||||
e.pretty_print("<deprecations test>", blueprint)
|
||||
raise AssertionError()
|
||||
|
||||
def test_class_deprecation(self):
|
||||
if Gtk.check_version(4, 10, 0) is not None:
|
||||
self.skipTest(f"Gtk.Dialog is not deprecated in GTK {self.gtkVersion}")
|
||||
|
||||
blueprint = """
|
||||
using Gtk 4.0;
|
||||
|
||||
Dialog {
|
||||
use-header-bar: 1;
|
||||
}
|
||||
"""
|
||||
message = "Gtk.Dialog is deprecated"
|
||||
|
||||
self.assertDeprecation(blueprint, message)
|
||||
|
||||
def test_property_deprecation(self):
|
||||
self.skipTest(
|
||||
"gobject-introspection does not currently write property deprecations to the typelib. See <https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/410>."
|
||||
)
|
||||
|
||||
if Gtk.check_version(4, 4, 0) is not None:
|
||||
self.skipTest(
|
||||
f"Gtk.DropTarget:drop is not deprecated in GTK {self.gtkVersion}"
|
||||
)
|
||||
|
||||
blueprint = """
|
||||
using Gtk 4.0;
|
||||
|
||||
$MyObject {
|
||||
a: bind drop_target.drop;
|
||||
}
|
||||
|
||||
DropTarget drop_target {
|
||||
}
|
||||
"""
|
||||
|
||||
message = "Gtk.DropTarget:drop is deprecated"
|
||||
|
||||
self.assertDeprecation(blueprint, message)
|
||||
|
||||
def test_signal_deprecation(self):
|
||||
if Gtk.check_version(4, 10, 0) is not None:
|
||||
self.skipTest(
|
||||
f"Gtk.Window::keys-changed is not deprecated in GTK {self.gtkVersion}"
|
||||
)
|
||||
|
||||
blueprint = """
|
||||
using Gtk 4.0;
|
||||
|
||||
Window {
|
||||
keys-changed => $handler();
|
||||
}
|
||||
"""
|
||||
|
||||
message = "signal Gtk.Window::keys-changed () is deprecated"
|
||||
|
||||
self.assertDeprecation(blueprint, message)
|
Loading…
Add table
Add a link
Reference in a new issue