├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── warn_supsect_pr.yml ├── .gitignore ├── .gitmodules ├── COPYRIGHT ├── LICENSE ├── README.md ├── changelog.md ├── server ├── .vscode │ ├── launch.json │ └── tasks.json ├── Cargo.toml ├── additional_stubs │ └── lxml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── lxml │ │ ├── ElementInclude.pyi │ │ ├── __init__.pyi │ │ ├── _types.pyi │ │ ├── builder.pyi │ │ ├── cssselect.pyi │ │ ├── etree │ │ │ ├── __init__.pyi │ │ │ ├── _classlookup.pyi │ │ │ ├── _cleanup.pyi │ │ │ ├── _docloader.pyi │ │ │ ├── _dtd.pyi │ │ │ ├── _element.pyi │ │ │ ├── _factory_func.pyi │ │ │ ├── _iterparse.pyi │ │ │ ├── _module_func.pyi │ │ │ ├── _module_misc.pyi │ │ │ ├── _nsclasses.pyi │ │ │ ├── _parser.pyi │ │ │ ├── _relaxng.pyi │ │ │ ├── _saxparser.pyi │ │ │ ├── _serializer.pyi │ │ │ ├── _xinclude.pyi │ │ │ ├── _xmlerror.pyi │ │ │ ├── _xmlid.pyi │ │ │ ├── _xmlschema.pyi │ │ │ ├── _xpath.pyi │ │ │ └── _xslt.pyi │ │ ├── html │ │ │ ├── __init__.pyi │ │ │ ├── _element.pyi │ │ │ ├── _form.pyi │ │ │ ├── _funcs.pyi │ │ │ ├── _parse.pyi │ │ │ ├── builder.pyi │ │ │ ├── clean.pyi │ │ │ ├── defs.pyi │ │ │ ├── diff.pyi │ │ │ ├── html5parser.pyi │ │ │ └── soupparser.pyi │ │ ├── isoschematron.pyi │ │ ├── objectify │ │ │ ├── __init__.pyi │ │ │ ├── _annotate.pyi │ │ │ ├── _element.pyi │ │ │ ├── _factory.pyi │ │ │ └── _misc.pyi │ │ ├── py.typed │ │ └── sax.pyi │ │ └── pyproject.toml ├── benches │ └── iai_profiler.rs ├── error_code.md ├── src │ ├── allocator.rs │ ├── args.rs │ ├── cli_backend.rs │ ├── constants.rs │ ├── core │ │ ├── config.rs │ │ ├── entry_point.rs │ │ ├── evaluation.rs │ │ ├── file_mgr.rs │ │ ├── import_resolver.rs │ │ ├── mod.rs │ │ ├── model.rs │ │ ├── odoo.rs │ │ ├── python_arch_builder.rs │ │ ├── python_arch_builder_hooks.rs │ │ ├── python_arch_eval.rs │ │ ├── python_arch_eval_hooks.rs │ │ ├── python_odoo_builder.rs │ │ ├── python_utils.rs │ │ ├── python_validator.rs │ │ └── symbols │ │ │ ├── class_symbol.rs │ │ │ ├── compiled_symbol.rs │ │ │ ├── disk_dir_symbol.rs │ │ │ ├── file_symbol.rs │ │ │ ├── function_symbol.rs │ │ │ ├── mod.rs │ │ │ ├── module_symbol.rs │ │ │ ├── namespace_symbol.rs │ │ │ ├── package_symbol.rs │ │ │ ├── root_symbol.rs │ │ │ ├── symbol.rs │ │ │ ├── symbol_mgr.rs │ │ │ └── variable_symbol.rs │ ├── features │ │ ├── ast_utils.rs │ │ ├── completion.rs │ │ ├── definition.rs │ │ ├── document_symbols.rs │ │ ├── features_utils.rs │ │ ├── hover.rs │ │ └── mod.rs │ ├── lib.rs │ ├── main.rs │ ├── server.rs │ ├── tasks.rs │ ├── threads.rs │ └── utils.rs └── tests │ ├── data │ ├── addons │ │ ├── module_1 │ │ │ ├── __init__.py │ │ │ ├── __manifest__.py │ │ │ ├── constants │ │ │ │ ├── __init__.py │ │ │ │ └── data │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── constants.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── base_test_models.py │ │ │ │ ├── models.py │ │ │ │ └── to_complete.py │ │ │ └── not_loaded │ │ │ │ └── not_loaded_file.py │ │ ├── module_2 │ │ │ ├── __init__.py │ │ │ ├── __manifest__.py │ │ │ └── models │ │ │ │ └── __init__.py │ │ └── not_a_module │ │ │ └── __init__.py │ └── python │ │ └── expressions │ │ ├── assign.py │ │ └── sections.py │ ├── module_1_structure.json │ ├── setup │ ├── mod.rs │ ├── setup.rs │ └── setup_constants.rs │ ├── test_basics.rs │ ├── test_ls.rs │ └── test_setup.rs └── vscode ├── .eslintrc.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── COPYRIGHT ├── LICENSE ├── README.md ├── SECURITY.md ├── build_package.sh ├── client ├── common │ ├── cleanup.mjs │ ├── constants.ts │ ├── events.ts │ ├── python.ts │ ├── safeLanguageClient.d.ts │ ├── safeLanguageClient.js │ ├── utils.ts │ └── validation.ts ├── extension.ts ├── global.d.ts ├── migration │ └── migrateConfig.ts └── views │ ├── changelog │ ├── body.html │ ├── changelogWebview.ts │ └── style.css │ ├── configurations │ ├── configurationWebView.html │ ├── configurationWebView.js │ ├── configurationWebView.ts │ └── style.css │ ├── crash_report │ ├── body.html │ ├── crashReport.ts │ ├── script.js │ └── style.css │ └── welcome │ ├── style.css │ ├── welcomeAlertView.html │ ├── welcomeWebView.html │ ├── welcomeWebView.js │ └── welcomeWebView.ts ├── images ├── advanced_hover_def.gif ├── autocomplete.png ├── autocompletion2.png ├── autocompletion3.png ├── autocompletion4.png ├── autocompletion5.png ├── diagnostics.png ├── diagnostics2.png ├── help_1.png ├── help_2.png ├── help_3.png ├── help_4.png ├── odoo_favicon └── odoo_logo.png ├── noxfile.py ├── package.json ├── tsconfig.json └── vscode.code-workspace /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us finding bugs 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Your setup** 11 | Version: 12 | Operating System: 13 | IDE and/or Integration tool (for example: Vscode - official extension): 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To Reproduce** 19 | Steps to reproduce the behavior: 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/workflows/warn_supsect_pr.yml: -------------------------------------------------------------------------------- 1 | name: Alert on Specific File Change and Println Detection 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | 9 | jobs: 10 | check-file-changes: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 2 17 | 18 | - name: Check for file changes 19 | id: check_changes 20 | run: | 21 | if git diff --name-only HEAD^ HEAD | grep -E 'server/Cargo.toml|server/src/constants.rs'; then 22 | echo "file_changed=true" >> $GITHUB_ENV 23 | else 24 | echo "file_changed=false" >> $GITHUB_ENV 25 | fi 26 | 27 | - name: Check for println occurrences 28 | id: check_println 29 | run: | 30 | if git diff HEAD^ HEAD | grep -E 'println\!\('; then 31 | echo "println_found=true" >> $GITHUB_ENV 32 | else 33 | echo "println_found=false" >> $GITHUB_ENV 34 | fi 35 | 36 | - name: Comment on PR if file changed or println detected 37 | if: env.file_changed == 'true' || env.println_found == 'true' 38 | uses: actions/github-script@v7 39 | with: 40 | github-token: ${{ secrets.GITHUB_TOKEN }} 41 | script: | 42 | let body = ""; 43 | if (process.env.file_changed === 'true') { 44 | body += "⚠️ The file 'Cargo.toml or constant.rs' has been modified in this PR. Please review the changes carefully.\n"; 45 | } 46 | if (process.env.println_found === 'true') { 47 | body += "⚠️ A 'println!(' statement was found in the commit. Please ensure debug statements are removed before merging.\n"; 48 | } 49 | github.rest.issues.createComment({ 50 | issue_number: context.payload.pull_request.number, 51 | owner: context.repo.owner, 52 | repo: context.repo.repo, 53 | body: body 54 | }); 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | client/server 4 | .idea 5 | .vscode-test 6 | .vscode/settings.json 7 | server/libs/ 8 | server/target/ 9 | server/logs/ 10 | env 11 | *.pyc 12 | *.log 13 | *.vsix 14 | *.lock 15 | vscode/package-lock.json 16 | vscode/.nox 17 | vscode/__pycache__ 18 | vscode/requirements.txt 19 | vscode/server 20 | vscode/CHANGELOG.md 21 | vscode/package-lock.json 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "server/typeshed"] 2 | path = server/typeshed 3 | url = https://github.com/python/typeshed 4 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | 2 | Most of the files are 3 | 4 | Copyright (c) 2004-2015 Odoo S.A. 5 | 6 | Many files also contain contributions from third 7 | parties. In this case the original copyright of 8 | the contributions can be traced through the 9 | history of the source version control system. 10 | 11 | When that is not the case, the files contain a prominent 12 | notice stating the original copyright and applicable 13 | license, or come with their own dedicated COPYRIGHT 14 | and/or LICENSE file. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Odoo Language Server 2 | 3 | This repository contains a language server for the Odoo framework that will provide autocompletion, file validation, hover requests, go to definition, and more. This language server is made available for your favorite IDE with the different extensions of this repository. 4 | Actually only vscode is available, but others will come later. 5 | To learn more about language servers, read https://microsoft.github.io/language-server-protocol/ 6 | Please consult the readme of each directory to learn more about each project. 7 | 8 | ## Table of Contents 9 | 10 | - [List of projects](#list-of-projects) 11 | - [State of the project](#state-of-the-project) 12 | - [Contributing](#contributing) 13 | - [License](#license) 14 | 15 | ## List of projects 16 | 17 | ### Language Server 18 | 19 | A generic language server that can be used to provide common IDE features to your IDE: autocompletion, Hovering, go to definition, etc... 20 | 21 | ### VsCode Extension 22 | 23 | An extension that will bundle the Odoo Language Server and give needed settings and some UI improvements to your vscode. 24 | 25 | ### Vim extension 26 | 27 | An integration of OdooLS is available in a side-project for now. Check it out here: https://github.com/Whenrow/odoo-ls.nvim 28 | 29 | ## State of the project 30 | 31 | All modules in this repository are actually in development and not released in a stable and valid version. You can face crashs or inconsistent results by using it. Please consult each directory to get a better idea of the state of each project. 32 | 33 | ## Branches description 34 | 35 | `master` contains all new merged content 36 | `alpha` contains all features that are freezed for the next beta version and tested internally 37 | `beta` contains the latest pre-released public version (downloadable packages, available on marketplace that supports pre-release tags) 38 | `release` contains the latest released public version (downloadable packages, available on marketplace) 39 | 40 | ## Contributing 41 | 42 | Do not hesitate to create [issues](https://github.com/odoo/odoo-ls/issues) or to open a [discussion](https://github.com/odoo/odoo-ls/discussions) would you have any problem or remark about the projects. Do not hesitate to browse the [wiki](https://github.com/odoo/odoo-ls/wiki) too. 43 | 44 | ## License 45 | 46 | All the projects of this repository is licensed under the LGPLv3 license. You can consult the LICENSE file to get more information about it. 47 | -------------------------------------------------------------------------------- /server/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "1.0.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Server (lldb)", 7 | "type": "lldb", 8 | "request": "launch", 9 | "args": ["--use-tcp"], 10 | "cargo": { 11 | "args": [ 12 | "build" 13 | ] 14 | }, 15 | "cwd": "${workspaceFolder}", 16 | "console": "externalTerminal" 17 | }, 18 | { 19 | "name": "Debug Test (lldb)", 20 | "type": "lldb", 21 | "request": "launch", 22 | "program": "${workspaceRoot}/target/debug/deps/test_ls-7a1baaf6ea83c2e3", 23 | "args": ["--use-tcp"], 24 | "cwd": "${workspaceFolder}", 25 | "console": "externalTerminal" 26 | }, 27 | { 28 | "name": "Launch Server (cppvsdbg)", 29 | "type": "cppvsdbg", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/target/debug/odoo_ls_server.exe", 32 | "args": ["--use-tcp"], 33 | "cwd": "${workspaceFolder}", 34 | "console": "externalTerminal", 35 | "preLaunchTask": "cargo build" 36 | }, 37 | { 38 | "name": "Debug Test (cppvsdbg)", 39 | "type": "cppvsdbg", 40 | "request": "launch", 41 | "program": "${workspaceRoot}/target/debug/deps/test_ls-41ae513a18d41487.exe", 42 | "cwd": "${workspaceFolder}", 43 | "console": "externalTerminal", 44 | "preLaunchTask": "cargo build" 45 | }, 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /server/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "cargo build", 6 | "type": "shell", 7 | "command": "cargo", 8 | "args": [ 9 | "build" 10 | ], 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | }, 15 | "problemMatcher": [ 16 | "$rustc" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "odoo_ls_server" 3 | version = "0.6.3" 4 | edition = "2021" 5 | authors = ["Odoo"] 6 | readme = "../README.md" 7 | repository = "https://github.com/odoo/odoo-ls" 8 | license = "../LICENSE" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = "1.0.79" 14 | clap = { version = "4.5.4", features = ["derive"] } 15 | glob = "0.3.1" 16 | regex = "1.10.3" 17 | ropey = "1.6.1" 18 | ruff_python_ast = { git = "https://github.com/astral-sh/ruff", tag = "0.11.4", version = "0.0.0" } 19 | ruff_python_parser = { git = "https://github.com/astral-sh/ruff", tag = "0.11.4", version = "0.0.0" } 20 | ruff_text_size = { git = "https://github.com/astral-sh/ruff", tag = "0.11.4", version = "0.0.0" } 21 | lsp-server = { git = "https://github.com/rust-lang/rust-analyzer", tag = "2024-06-17", version = "0.7.6" } 22 | serde = "1.0.195" 23 | serde_json = "1.0.111" 24 | url = "2.5.0" 25 | weak-table = "0.3.2" 26 | lsp-types = "0.97.0" 27 | crossbeam-channel = "0.5.13" 28 | path-slash = "0.2.1" 29 | tracing = "0.1.40" 30 | tracing-subscriber = "0.3.18" 31 | tracing-appender = "0.2.3" 32 | tracing-panic = "0.1.2" 33 | winapi = { version = "0.3.9", features = ["winbase", "processthreadsapi", "synchapi", "handleapi"] } 34 | ctrlc = "3.4.4" 35 | once_cell = "1.20.1" 36 | itertools = "0.14.0" 37 | byteyarn = "0.5.1" 38 | [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] 39 | nix = { version = "0.29.0", features = ["process"] } 40 | 41 | [[bench]] 42 | name = "iai_profiler" 43 | harness = false 44 | 45 | [dev-dependencies] 46 | iai-callgrind = "0.14.0" 47 | 48 | [features] 49 | default = [] 50 | debug_yarn = [] # use a readable structure instead of Yarn for debug purpose 51 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | This project implements an external python type annotation package, which are used by static type checkers and IDEs for providing diagnostics and hints. 4 | 5 | Anything publicly distributed, especially installable package on [Python Package Index (PyPI)](https://pypi.org/), are not supposed to be executed in any way. Source distribution contains some part of internal test suite which is only for checking integrity and correctness of this package itself, and shouldn't be touchable by external users. 6 | 7 | That said, it is hard to be 100% ascertain how static type checkers would behave. While [`pyright`](https://github.com/microsoft/pyright) is written in Typescript and therefore won't be able to execute any Python code, [`mypy`](https://github.com/python/mypy) has some concern that can't be overlooked. 8 | 9 | For example, `mypy` provides `--install-types` option to install external annotation packages, which can execute arbitrary python code during setup. Although `mypy` has decided to not install `types-lxml` package by default, it is impossible to make claim on anything happening in future. If suspicion arises which have security implications, please [report to `mypy` repository](https://github.com/python/mypy/issues). This project will not shoulder any responsibility which is caused by `mypy` behavior. 10 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/ElementInclude.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 11): 4 | from typing import LiteralString 5 | else: 6 | from typing_extensions import LiteralString 7 | 8 | from typing import Literal, Protocol, overload 9 | 10 | from lxml.etree import LxmlSyntaxError, _Element 11 | 12 | from ._types import Unused, _ElementOrTree 13 | 14 | # exported constants 15 | XINCLUDE: LiteralString 16 | XINCLUDE_INCLUDE: LiteralString 17 | XINCLUDE_FALLBACK: LiteralString 18 | XINCLUDE_ITER_TAG: LiteralString 19 | DEFAULT_MAX_INCLUSION_DEPTH: int 20 | 21 | class FatalIncludeError(LxmlSyntaxError): ... 22 | class LimitedRecursiveIncludeError(FatalIncludeError): ... 23 | 24 | # The default_loader() in lxml.ElementInclude is completely 25 | # retired (lxml uses its own internal loader) 26 | 27 | class LoaderProtocol(Protocol): 28 | """Protocol for loader func argument in `ElementInclude.include()` 29 | 30 | Annotation 31 | ---------- 32 | `loader=` argument in `ElementInclude.include()` specifies the function 33 | object to load URL or file resource. It has the following overloaded 34 | function signature: 35 | - `(_href: str, _mode: Literal["xml"], /, encoding: str = None) -> _Element` 36 | - `(_href: str, _mode: Literal["text"], /, encoding: str = None) -> str` 37 | """ 38 | 39 | @overload 40 | def __call__( 41 | self, 42 | _href: str, # URL or local path from href="..." attribute 43 | _mode: Literal["xml"], 44 | /, 45 | encoding: Unused = None, # Under XML mode this param is ignored 46 | # but must be present nontheless 47 | ) -> _Element: ... 48 | @overload 49 | def __call__( 50 | self, 51 | _href: str, 52 | _mode: Literal["text"], 53 | /, 54 | encoding: str | None = None, 55 | ) -> str: ... 56 | 57 | def include( 58 | elem: _ElementOrTree, 59 | loader: LoaderProtocol | None = None, 60 | base_url: str | None = None, 61 | max_depth: int = 6, 62 | ) -> None: 63 | """Expand XInclude directives 64 | 65 | Annotation 66 | ---------- 67 | - Source documentation above `include()` is outdated; this function 68 | does not return at all. 69 | - Try using `from lxml.ElementInclude import LoaderProtocol` from 70 | within IDEs to lookup its purpose and usage. This is annotation 71 | only and doesn't exist in lxml source. 72 | """ 73 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/__init__.pyi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/server/additional_stubs/lxml/lxml/__init__.pyi -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/builder.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Generic, Mapping, Protocol, overload 2 | 3 | from ._types import _AnyStr, _ElementFactory, _ET_co, _NSMapArg, _NSTuples, _TagName 4 | from .etree import CDATA, _Element 5 | 6 | # Mapping should have been something like 7 | # Mapping[type[_T], Callable[[_Element, _T], None]] 8 | # but invariant key/value causes it to be incompatible 9 | # with anything 10 | _TypeMapArg = Mapping[Any, Callable[[_Element, Any], None]] 11 | 12 | class _EMakerCallProtocol(Protocol[_ET_co]): 13 | def __call__( 14 | self, 15 | # By default ElementMaker only accepts _Element and types 16 | # interpretable by default typemap (str, CDATA and dict). 17 | # Typemap can be expanded manually to accept object of 18 | # any type, but such usage isn't very common, so we 19 | # concentrate on default user case instead. 20 | # Extra notes: 21 | # - Although builder expects to be nested, its 22 | # implementation allows just any function object 23 | # as children 24 | # - Child element type need not follow parent type. 25 | # This is much more apparent in e.g. HtmlElement 26 | *_children: _Element 27 | | str 28 | | CDATA 29 | | dict[str, Any] 30 | | Callable[[], _Element | str | CDATA | dict[str, Any]], 31 | **_attrib: str, 32 | ) -> _ET_co: ... 33 | # Following properties come from functools.partial 34 | @property 35 | def func(self) -> ElementMaker[_ET_co]: ... 36 | @property 37 | def args(self) -> tuple[str]: ... 38 | @property 39 | def keywords(self) -> dict[str, Any]: ... 40 | 41 | # One might be tempted to use artibrary callable in 42 | # makeelement argument, because ElementMaker 43 | # constructor can actually accept any callable as 44 | # makeelement. However all element creation attempt 45 | # would fail, as 'nsmap' keyword argument is expected 46 | # to be usable in the makeelement function call. 47 | class ElementMaker(Generic[_ET_co]): 48 | @overload # makeelement is keyword 49 | def __new__( 50 | cls, 51 | typemap: _TypeMapArg | None = None, 52 | namespace: str | None = None, 53 | nsmap: _NSMapArg | _NSTuples | None = None, # dict() 54 | *, 55 | makeelement: _ElementFactory[_ET_co], 56 | ) -> ElementMaker[_ET_co]: ... 57 | @overload # makeelement is positional 58 | def __new__( 59 | cls, 60 | typemap: _TypeMapArg | None, 61 | namespace: str | None, 62 | nsmap: _NSMapArg | _NSTuples | None, 63 | makeelement: _ElementFactory[_ET_co], 64 | ) -> ElementMaker[_ET_co]: ... 65 | @overload # makeelement is default or absent 66 | def __new__( 67 | cls, 68 | typemap: _TypeMapArg | None = None, 69 | namespace: str | None = None, 70 | nsmap: _NSMapArg | _NSTuples | None = None, 71 | makeelement: None = None, 72 | ) -> ElementMaker: ... 73 | def __call__( 74 | self, 75 | tag: _TagName, 76 | *_children: _Element # See _EMakerCallProtocol above 77 | | str 78 | | CDATA 79 | | dict[str, Any] 80 | | Callable[[], _Element | str | CDATA | dict[str, Any]], 81 | **_attrib: str, 82 | ) -> _ET_co: ... 83 | 84 | # __getattr__ here is special. ElementMaker supports using any 85 | # attribute name as tag, returning a functools.partial 86 | # object to ElementMaker.__call__() with tag argument prefilled. 87 | # So E('html', ...) is equivalent to E.html(...). 88 | # However, annotation of returning partial is vetoed, 89 | # as it has a very generic call signature in typeshed. 90 | # The confined call signature is more important for 91 | # users. So opt for adding partial properties to the protocol. 92 | def __getattr__(self, name: str) -> _EMakerCallProtocol[_ET_co]: ... 93 | 94 | # Private read-only attributes, could be useful for understanding 95 | # how the ElementMaker is constructed 96 | # Note that the corresponding input arguments during ElementMaker 97 | # instantiation are much more relaxed, as typing.Mapping argument 98 | # invariance has posed some challenge to typing. We can afford 99 | # some more restriction as return value or attribute. 100 | @property 101 | def _makeelement(self) -> _ElementFactory[_ET_co]: ... 102 | @property 103 | def _namespace(self) -> str | None: ... 104 | @property 105 | def _nsmap(self) -> dict[str | None, _AnyStr] | None: ... 106 | @property 107 | def _typemap(self) -> dict[type[Any], Callable[[_ET_co, Any], None]]: ... 108 | 109 | E: ElementMaker 110 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/cssselect.pyi: -------------------------------------------------------------------------------- 1 | from typing import Literal, overload 2 | 3 | import cssselect as _csel 4 | from cssselect.parser import Function 5 | from cssselect.xpath import XPathExpr 6 | 7 | from ._types import _ET, _ElementOrTree, _NonDefaultNSMapArg, _XPathVarArg 8 | from .etree import XPath 9 | from .html import HtmlElement 10 | from .objectify import ObjectifiedElement 11 | 12 | _CSSTransArg = LxmlTranslator | Literal["xml", "html", "xhtml"] 13 | 14 | SelectorError = _csel.SelectorError 15 | SelectorSyntaxError = _csel.SelectorSyntaxError 16 | ExpressionError = _csel.ExpressionError 17 | 18 | class LxmlTranslator(_csel.GenericTranslator): 19 | def xpath_contains_function( 20 | self, xpath: XPathExpr, function: Function 21 | ) -> XPathExpr: ... 22 | 23 | class LxmlHTMLTranslator(LxmlTranslator, _csel.HTMLTranslator): 24 | pass 25 | 26 | class CSSSelector(XPath): 27 | # Although 'css' is implemented as plain attribute, it is 28 | # meaningless to modify it, because instance is initialized 29 | # with translated XPath expression, not the CSS expression. 30 | # Allowing attribute modification would cause confusion as 31 | # CSS expression and the underlying XPath expression don't 32 | # match. 33 | @property 34 | def css(self) -> str: ... 35 | def __init__( 36 | self, 37 | css: str, 38 | namespaces: _NonDefaultNSMapArg | None = None, 39 | translator: _CSSTransArg = "xml", 40 | ) -> None: ... 41 | # It is safe to assume cssselect always selects element 42 | # representable in original element tree, because CSS 43 | # expression is transformed into XPath via css_to_xpath() 44 | # which doesn't support pseudo-element by default. 45 | # OTOH, the overload situation is similar to SubElement(); 46 | # we handle the 2 built-in element families (HtmlElement 47 | # and ObjectifiedElement), but the rest is up to users. 48 | @overload 49 | def __call__( 50 | self, 51 | _etree_or_element: _ElementOrTree[ObjectifiedElement], 52 | /, 53 | **_variables: _XPathVarArg, 54 | ) -> list[ObjectifiedElement]: ... 55 | @overload 56 | def __call__( 57 | self, 58 | _etree_or_element: _ElementOrTree[HtmlElement], 59 | /, 60 | **_variables: _XPathVarArg, 61 | ) -> list[HtmlElement]: ... 62 | @overload 63 | def __call__( 64 | self, 65 | _etree_or_element: _ElementOrTree[_ET], 66 | /, 67 | **_variables: _XPathVarArg, 68 | ) -> list[_ET]: ... 69 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_cleanup.pyi: -------------------------------------------------------------------------------- 1 | from typing import Collection, Iterable, overload 2 | 3 | from .._types import _AnyStr, _ElementOrTree, _NSMapArg, _TagSelector 4 | 5 | def cleanup_namespaces( 6 | tree_or_element: _ElementOrTree, 7 | top_nsmap: _NSMapArg | None = None, 8 | keep_ns_prefixes: Iterable[_AnyStr] | None = None, 9 | ) -> None: ... 10 | 11 | # For functions below, the first `tree_or_element` argument 12 | # can never be keyword argument, since tag/attribute names 13 | # that followed are considered positional arguments in 14 | # all possible function signature overloads. 15 | 16 | @overload 17 | def strip_attributes( 18 | __tree_or_elem: _ElementOrTree, 19 | *attribute_names: str, 20 | ) -> None: ... 21 | @overload 22 | def strip_attributes( 23 | __tree_or_elem: _ElementOrTree, __attrib: Collection[str], / 24 | ) -> None: ... 25 | @overload 26 | def strip_elements( 27 | __tree_or_elem: _ElementOrTree, 28 | *tag_names: _TagSelector, 29 | with_tail: bool = True, 30 | ) -> None: ... 31 | @overload 32 | def strip_elements( 33 | __tree_or_elem: _ElementOrTree, 34 | __tag: Collection[_TagSelector], 35 | /, 36 | with_tail: bool = True, 37 | ) -> None: ... 38 | @overload 39 | def strip_tags( 40 | __tree_or_elem: _ElementOrTree, 41 | *tag_names: _TagSelector, 42 | ) -> None: ... 43 | @overload 44 | def strip_tags( 45 | __tree_or_elem: _ElementOrTree, __tag: Collection[_TagSelector], / 46 | ) -> None: ... 47 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_docloader.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | from abc import ABCMeta, abstractmethod 3 | from typing import Any, final, type_check_only 4 | 5 | from _typeshed import SupportsRead 6 | 7 | if sys.version_info >= (3, 11): 8 | from typing import Self 9 | else: 10 | from typing_extensions import Self 11 | 12 | from .._types import _AnyStr 13 | 14 | @type_check_only 15 | class _InputDocument: 16 | """An internal opaque object used as resolver result""" 17 | 18 | @type_check_only 19 | class _ResolverContext: 20 | """An internal opaque object used in resolve methods""" 21 | 22 | class Resolver(metaclass=ABCMeta): 23 | @abstractmethod 24 | def resolve( 25 | self, system_url: str, public_id: str, context: _ResolverContext 26 | ) -> _InputDocument | None: ... 27 | def resolve_empty(self, context: _ResolverContext) -> _InputDocument: ... 28 | def resolve_string( 29 | self, 30 | string: _AnyStr, 31 | context: _ResolverContext, 32 | *, 33 | base_url: _AnyStr | None = None, 34 | ) -> _InputDocument: ... 35 | def resolve_filename( 36 | self, filename: _AnyStr, context: _ResolverContext 37 | ) -> _InputDocument: ... 38 | def resolve_file( 39 | self, 40 | f: SupportsRead[Any], 41 | context: _ResolverContext, 42 | *, 43 | base_url: _AnyStr | None, 44 | close: bool, 45 | ) -> _InputDocument: ... 46 | 47 | @final 48 | class _ResolverRegistry: 49 | def add(self, resolver: Resolver) -> None: ... 50 | def remove(self, resolver: Resolver) -> None: ... 51 | def copy(self) -> Self: ... 52 | # Wonder if resolve() should be removed. It's not like one 53 | # can supply the internal context object at all. So far it 54 | # is only used internally. 55 | def resolve( 56 | self, system_url: str, public_id: str, context: _ResolverContext 57 | ) -> _InputDocument | None: ... 58 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_dtd.pyi: -------------------------------------------------------------------------------- 1 | from typing import Iterator, Literal, final, overload 2 | 3 | from .._types import _ElementOrTree, _FileReadSource 4 | from ._module_misc import LxmlError, _Validator 5 | 6 | class DTDError(LxmlError): ... 7 | class DTDParseError(DTDError): ... 8 | class DTDValidateError(DTDError): ... 9 | 10 | @final 11 | class _DTDElementContentDecl: 12 | @property 13 | def name(self) -> str | None: ... 14 | @property 15 | def type(self) -> Literal["pcdata", "element", "seq", "or"] | None: ... 16 | @property 17 | def occur(self) -> Literal["once", "opt", "mul", "plus"] | None: ... 18 | @property 19 | def left(self) -> _DTDElementContentDecl | None: ... 20 | @property 21 | def right(self) -> _DTDElementContentDecl | None: ... 22 | 23 | @final 24 | class _DTDAttributeDecl: 25 | @property 26 | def name(self) -> str | None: ... 27 | @property 28 | def elemname(self) -> str | None: ... 29 | @property 30 | def prefix(self) -> str | None: ... 31 | @property 32 | def type( 33 | self, 34 | ) -> ( 35 | Literal[ 36 | "cdata", 37 | "id", 38 | "idref", 39 | "idrefs", 40 | "entity", 41 | "entities", 42 | "nmtoken", 43 | "nmtokens", 44 | "enumeration", 45 | "notation", 46 | ] 47 | | None 48 | ): ... 49 | @property 50 | def default(self) -> Literal["none", "required", "implied", "fixed"] | None: ... 51 | @property 52 | def default_value(self) -> str | None: ... 53 | def itervalues(self) -> Iterator[str]: ... 54 | def values(self) -> list[str]: ... 55 | 56 | @final 57 | class _DTDElementDecl: 58 | @property 59 | def name(self) -> str | None: ... 60 | @property 61 | def prefix(self) -> str | None: ... 62 | @property 63 | def type( 64 | self, 65 | ) -> Literal["undefined", "empty", "any", "mixed", "element"] | None: ... 66 | @property 67 | def content(self) -> _DTDElementContentDecl | None: ... 68 | def iterattributes(self) -> Iterator[_DTDAttributeDecl]: ... 69 | def attributes(self) -> list[_DTDAttributeDecl]: ... 70 | 71 | @final 72 | class _DTDEntityDecl: 73 | @property 74 | def name(self) -> str | None: ... 75 | @property 76 | def orig(self) -> str | None: ... 77 | @property 78 | def content(self) -> str | None: ... 79 | @property 80 | def system_url(self) -> str | None: ... 81 | 82 | class DTD(_Validator): 83 | # external_id is effective only when file is None, and 84 | # native string raises exception 85 | @overload 86 | def __init__(self, file: _FileReadSource) -> None: ... 87 | @overload 88 | def __init__(self, file: None = None, *, external_id: bytes) -> None: ... 89 | @property 90 | def name(self) -> str | None: ... 91 | @property 92 | def external_id(self) -> str | None: ... 93 | @property 94 | def system_url(self) -> str | None: ... 95 | def iterelements(self) -> Iterator[_DTDElementDecl]: ... 96 | def elements(self) -> list[_DTDElementDecl]: ... 97 | def iterentities(self) -> Iterator[_DTDEntityDecl]: ... 98 | def entities(self) -> list[_DTDEntityDecl]: ... 99 | def __call__(self, etree: _ElementOrTree) -> bool: ... 100 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_factory_func.pyi: -------------------------------------------------------------------------------- 1 | from typing import overload 2 | 3 | from .._types import ( 4 | _ET, 5 | SupportsLaxedItems, 6 | _AnyStr, 7 | _DefEtreeParsers, 8 | _ElementFactory, 9 | _ET_co, 10 | _FileReadSource, 11 | _NSMapArg, 12 | _TagName, 13 | ) 14 | from ..html import HtmlElement 15 | from ..objectify import ObjectifiedElement, StringElement 16 | from ._element import _Comment, _ElementTree, _Entity, _ProcessingInstruction 17 | 18 | def Comment(text: _AnyStr | None = None) -> _Comment: ... 19 | def ProcessingInstruction( 20 | target: _AnyStr, text: _AnyStr | None = None 21 | ) -> _ProcessingInstruction: ... 22 | 23 | PI = ProcessingInstruction 24 | 25 | def Entity(name: _AnyStr) -> _Entity: ... 26 | 27 | Element: _ElementFactory 28 | 29 | # SubElement is a bit more complex than expected, as it 30 | # handles other kinds of element, like HtmlElement 31 | # and ObjectiedElement. 32 | # 33 | # - If parent is HtmlElement, generated subelement is 34 | # HtmlElement or its relatives, depending on the tag name 35 | # used. For example, with "label" as tag, it generates 36 | # a LabelElement. 37 | # 38 | # - For ObjectifiedElement, subelements generated this way 39 | # are always of type StringElement. Once the object is 40 | # constructed, the object type won't change, even when 41 | # type annotation attribute is modified. 42 | # OE users need to use E-factory for more flexibility. 43 | @overload 44 | def SubElement( # type: ignore[overload-overlap] 45 | _parent: ObjectifiedElement, 46 | _tag: _TagName, 47 | /, 48 | attrib: SupportsLaxedItems[str, _AnyStr] | None = None, 49 | nsmap: _NSMapArg | None = None, 50 | **_extra: _AnyStr, 51 | ) -> StringElement: ... 52 | @overload 53 | def SubElement( 54 | _parent: HtmlElement, 55 | _tag: _TagName, 56 | /, 57 | attrib: SupportsLaxedItems[str, _AnyStr] | None = None, 58 | nsmap: _NSMapArg | None = None, 59 | **_extra: _AnyStr, 60 | ) -> HtmlElement: ... 61 | @overload 62 | def SubElement( 63 | _parent: _ET, 64 | _tag: _TagName, 65 | /, 66 | attrib: SupportsLaxedItems[str, _AnyStr] | None = None, 67 | nsmap: _NSMapArg | None = None, 68 | **_extra: _AnyStr, 69 | ) -> _ET: ... 70 | @overload # from element, parser ignored 71 | def ElementTree(element: _ET) -> _ElementTree[_ET]: ... 72 | @overload # from file source, custom parser 73 | def ElementTree( 74 | element: None = None, 75 | *, 76 | file: _FileReadSource, 77 | parser: _DefEtreeParsers[_ET_co], 78 | ) -> _ElementTree[_ET_co]: ... 79 | @overload # from file source, default parser 80 | def ElementTree( 81 | element: None = None, 82 | *, 83 | file: _FileReadSource, 84 | parser: None = None, 85 | ) -> _ElementTree: ... 86 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_module_misc.pyi: -------------------------------------------------------------------------------- 1 | # 2 | # lxml.etree helper classes, exceptions and constants 3 | # 4 | 5 | import sys 6 | from abc import ABCMeta, abstractmethod 7 | from typing import overload 8 | 9 | if sys.version_info >= (3, 11): 10 | from typing import LiteralString 11 | else: 12 | from typing_extensions import LiteralString 13 | 14 | from .._types import _AnyStr, _ElementOrTree, _TagName 15 | from ._dtd import DTD 16 | from ._element import _Element 17 | from ._xmlerror import _BaseErrorLog, _ListErrorLog 18 | 19 | DEBUG: int 20 | ICONV_COMPILED_VERSION: tuple[int, int] 21 | LIBXML_VERSION: tuple[int, int, int] 22 | LIBXML_COMPILED_VERSION: tuple[int, int, int] 23 | LXML_VERSION: tuple[int, int, int, int] 24 | __version__: LiteralString 25 | 26 | class DocInfo: 27 | # Can't be empty, otherwise it means tree contains no element 28 | @property 29 | def root_name(self) -> str: ... 30 | @property 31 | def public_id(self) -> str | None: ... 32 | @public_id.setter 33 | def public_id(self, __v: _AnyStr | None) -> None: ... 34 | @property 35 | def system_url(self) -> str | None: ... 36 | @system_url.setter 37 | def system_url(self, __v: _AnyStr | None) -> None: ... 38 | @property 39 | def xml_version(self) -> str: ... # fallback is "1.0" 40 | @property 41 | def encoding(self) -> str: ... # fallback is "UTF-8" or "ISO-8859-1" 42 | @property 43 | def standalone(self) -> bool | None: ... 44 | @property 45 | def URL(self) -> str | None: ... 46 | @URL.setter 47 | def URL(self, __v: _AnyStr | None) -> None: ... 48 | @property 49 | def doctype(self) -> str: ... 50 | @property 51 | def internalDTD(self) -> DTD | None: ... 52 | @property 53 | def externalDTD(self) -> DTD | None: ... 54 | def clear(self) -> None: ... 55 | 56 | class QName: 57 | @overload 58 | def __init__( 59 | self, 60 | text_or_uri_or_element: _TagName | _Element, 61 | tag: _TagName | None = None, 62 | ) -> None: ... 63 | @overload 64 | def __init__( 65 | self, 66 | text_or_uri_or_element: None, 67 | tag: _TagName, 68 | ) -> None: ... 69 | @property 70 | def localname(self) -> str: ... 71 | @property 72 | def namespace(self) -> str | None: ... 73 | @property 74 | def text(self) -> str: ... 75 | # Emulate __richcmp__() 76 | def __ge__(self, other: _TagName) -> bool: ... 77 | def __gt__(self, other: _TagName) -> bool: ... 78 | def __le__(self, other: _TagName) -> bool: ... 79 | def __lt__(self, other: _TagName) -> bool: ... 80 | 81 | class CDATA: 82 | def __init__(self, data: _AnyStr) -> None: ... 83 | 84 | class Error(Exception): ... 85 | 86 | class LxmlError(Error): 87 | def __init__( 88 | self, message: object, error_log: _BaseErrorLog | None = None 89 | ) -> None: ... 90 | # Even when LxmlError is initiated with PyErrorLog, it fools 91 | # error_log property by creating a dummy _ListErrorLog object 92 | error_log: _ListErrorLog 93 | 94 | class DocumentInvalid(LxmlError): ... 95 | class LxmlSyntaxError(LxmlError, SyntaxError): ... 96 | class C14NError(LxmlError): ... 97 | 98 | class _Validator(metaclass=ABCMeta): 99 | def assert_(self, etree: _ElementOrTree) -> None: ... 100 | def assertValid(self, etree: _ElementOrTree) -> None: ... 101 | def validate(self, etree: _ElementOrTree) -> bool: ... 102 | @property 103 | def error_log(self) -> _ListErrorLog: ... 104 | # all methods implicitly require a concrete __call__() 105 | # implementation in subclasses in order to be usable 106 | @abstractmethod 107 | def __call__(self, etree: _ElementOrTree) -> bool: ... 108 | 109 | # Though etree.Schematron is not implemented in stub, 110 | # lxml.isoschematron reuses related exception classes, 111 | # so list them here 112 | class SchematronError(LxmlError): 113 | """Base class of all Schematron errors""" 114 | 115 | class SchematronParseError(SchematronError): 116 | """Error while parsing an XML document as Schematron schema""" 117 | 118 | class SchematronValidateError(SchematronError): 119 | """Error while validating an XML document with a Schematron schema""" 120 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_nsclasses.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import ( 3 | Any, 4 | Callable, 5 | Iterable, 6 | Iterator, 7 | MutableMapping, 8 | TypeVar, 9 | final, 10 | overload, 11 | ) 12 | 13 | if sys.version_info >= (3, 10): 14 | from typing import ParamSpec 15 | else: 16 | from typing_extensions import ParamSpec 17 | 18 | from .._types import SupportsLaxedItems 19 | from ._classlookup import ElementBase, ElementClassLookup, FallbackElementClassLookup 20 | from ._module_misc import LxmlError 21 | 22 | _T = TypeVar("_T") 23 | _KT = TypeVar("_KT") 24 | _VT = TypeVar("_VT") 25 | _P = ParamSpec("_P") 26 | _Public_ET = TypeVar("_Public_ET", bound=ElementBase) 27 | 28 | class LxmlRegistryError(LxmlError): 29 | """Base class of lxml registry errors""" 30 | 31 | class NamespaceRegistryError(LxmlRegistryError): 32 | """Error registering a namespace extension""" 33 | 34 | # Yet another dict-wannabe that lacks many normal methods and add a few 35 | # of its own 36 | class _NamespaceRegistry(MutableMapping[_KT, _VT]): 37 | def __delitem__(self, __key: _KT) -> None: ... 38 | def __getitem__(self, __key: _KT) -> _VT: ... 39 | def __setitem__(self, __key: _KT, __value: _VT) -> None: ... 40 | def __iter__(self) -> Iterator[_KT]: ... 41 | def __len__(self) -> int: ... 42 | def update( # type: ignore[override] 43 | self, 44 | class_dict_iterable: SupportsLaxedItems[_KT, _VT] | Iterable[tuple[_KT, _VT]], 45 | ) -> None: ... 46 | def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override] 47 | def iteritems(self) -> Iterator[tuple[_KT, _VT]]: ... 48 | def clear(self) -> None: ... 49 | 50 | # 51 | # Element namespace 52 | # 53 | 54 | @final 55 | class _ClassNamespaceRegistry(_NamespaceRegistry[str | None, type[ElementBase]]): 56 | @overload # @ns(None), @ns('tag') 57 | def __call__( 58 | self, __tag: str | None, __cls: None = None 59 | ) -> Callable[[type[_Public_ET]], type[_Public_ET]]: ... 60 | @overload # plain @ns 61 | def __call__(self, __cls: type[_Public_ET]) -> type[_Public_ET]: ... 62 | 63 | class ElementNamespaceClassLookup(FallbackElementClassLookup): 64 | """Element class lookup scheme that searches the Element class in the 65 | Namespace registry 66 | 67 | Example 68 | ------- 69 | ```python 70 | lookup = ElementNamespaceClassLookup() 71 | ns_elements = lookup.get_namespace("http://schema.org/Movie") 72 | 73 | @ns_elements 74 | class movie(ElementBase): 75 | "Element implementation for 'movie' tag (using class name) in schema namespace." 76 | 77 | @ns_elements("movie") 78 | class MovieElement(ElementBase): 79 | "Element implementation for 'movie' tag (explicit tag name) in schema namespace." 80 | """ 81 | 82 | def __init__( 83 | self, 84 | fallback: ElementClassLookup | None = None, 85 | ) -> None: ... 86 | def get_namespace(self, ns_uri: str | None) -> _ClassNamespaceRegistry: 87 | """Retrieve the namespace object associated with the given URI 88 | 89 | Pass None for the empty namespace. 90 | Creates a new namespace object if it does not yet exist. 91 | """ 92 | 93 | # 94 | # Function namespace 95 | # 96 | 97 | @final 98 | class _FunctionNamespaceRegistry(_NamespaceRegistry[str, Callable[..., Any]]): 99 | @property 100 | def prefix(self) -> str: ... 101 | @prefix.setter 102 | def prefix(self, __v: str | None) -> None: ... 103 | @overload # @ns('name') 104 | def __call__( 105 | self, __name: str, __func: None = None 106 | ) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: ... 107 | @overload # plain @ns 108 | def __call__(self, __func: Callable[_P, _T]) -> Callable[_P, _T]: ... 109 | 110 | def FunctionNamespace(ns_uri: str | None) -> _FunctionNamespaceRegistry: ... 111 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_relaxng.pyi: -------------------------------------------------------------------------------- 1 | from typing import overload 2 | 3 | from .._types import _ElementOrTree, _FileReadSource 4 | from ._module_misc import LxmlError, _Validator 5 | 6 | class RelaxNGError(LxmlError): ... 7 | class RelaxNGParseError(RelaxNGError): ... 8 | class RelaxNGValidateError(RelaxNGError): ... 9 | 10 | class RelaxNG(_Validator): 11 | @overload 12 | def __init__(self, etree: _ElementOrTree) -> None: ... 13 | @overload 14 | def __init__( 15 | self, 16 | etree: None = None, 17 | *, 18 | file: _FileReadSource, 19 | ) -> None: ... 20 | def __call__(self, etree: _ElementOrTree) -> bool: ... 21 | @classmethod 22 | def from_rnc_string(cls, src: str, base_url: str | None = None) -> RelaxNG: ... 23 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_saxparser.pyi: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import Callable, Mapping, Protocol, TypeVar 3 | 4 | from .._types import _DefEtreeParsers, _ElementFactory 5 | from ._element import _Attrib, _Comment, _Element, _ProcessingInstruction 6 | from ._parser import XMLSyntaxError 7 | 8 | _T_co = TypeVar("_T_co", covariant=True) 9 | 10 | class XMLSyntaxAssertionError(XMLSyntaxError, AssertionError): ... 11 | 12 | class ParserTarget(Protocol[_T_co]): 13 | """This is a stub-only class representing parser target interface. 14 | 15 | - Because almost all methods are optional, ParserTarget should be 16 | explicitly inherited in code for type checking. See TreeBuilder 17 | and the snippet example below. 18 | - Some IDEs can do method signature autocompletion. See notes below. 19 | 20 | Example 21 | ------- 22 | ```python 23 | from lxml import etree 24 | if not TYPE_CHECKING: 25 | etree.ParserTarget = object 26 | 27 | class MyParserTarget(etree.ParserTarget): 28 | def __init__(self) -> None: ... 29 | def start(self, # 3 argument form is not autocompleted 30 | tag: str, attrib: _Attrib, nsmap: Mapping[str, str] = ..., 31 | ) -> None: ... 32 | # Do something 33 | def close(self) -> str: 34 | return "something" 35 | 36 | parser = etree.HTMLParser() # type is HTMLParser[_Element] 37 | result = parser.close() # _Element 38 | 39 | t1 = MyParserTarget() 40 | parser = etree.HTMLParser(target=t1) # mypy -> HTMLParser[Any] 41 | # pyright -> HTMLParser[Unknown] 42 | 43 | t2 = cast("etree.ParserTarget[str]", MyParserTarget()) 44 | parser = etree.HTMLParser(target=t2) # HTMLParser[str] 45 | result = parser.close() # str 46 | ``` 47 | 48 | Notes 49 | ----- 50 | - Only `close()` is mandatory. In extreme case, a vanilla class instance 51 | with noop `close()` is a valid null parser target that does nothing. 52 | - `start()` can take either 2 or 3 extra arguments. 53 | - Some methods are undocumented. They are included in stub nonetheless. 54 | 55 | See Also 56 | -------- 57 | - `_PythonSaxParserTarget()` in `src/lxml/parsertarget.pxi` 58 | - [Target parser official document](https://lxml.de/parsing.html#the-target-parser-interface) 59 | """ 60 | 61 | @abstractmethod 62 | def close(self) -> _T_co: ... 63 | def comment(self, text: str) -> None: ... 64 | def data(self, data: str) -> None: ... 65 | def end(self, tag: str) -> None: ... 66 | def start( 67 | self, 68 | tag: str, 69 | attrib: _Attrib | Mapping[str, str] | None, 70 | nsmap: Mapping[str, str] | None = None, 71 | ) -> None: ... 72 | # Methods below are undocumented. Lxml has described 73 | # 'start-ns' and 'end-ns' events however. 74 | def pi(self, target: str, data: str | None) -> None: ... 75 | # Default namespace prefix is empty string, not None 76 | def start_ns(self, prefix: str, uri: str) -> None: ... 77 | def end_ns(self, prefix: str) -> None: ... 78 | def doctype( 79 | self, 80 | root_tag: str | None, 81 | public_id: str | None, 82 | system_id: str | None, 83 | ) -> None: ... 84 | 85 | class TreeBuilder(ParserTarget[_Element]): 86 | def __init__( 87 | self, 88 | *, 89 | element_factory: _ElementFactory[_Element] | None = None, 90 | parser: _DefEtreeParsers | None = None, 91 | comment_factory: Callable[..., _Comment] | None = None, 92 | pi_factory: Callable[..., _ProcessingInstruction] | None = None, 93 | insert_comments: bool = True, 94 | insert_pis: bool = True, 95 | ) -> None: ... 96 | def close(self) -> _Element: ... 97 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_xinclude.pyi: -------------------------------------------------------------------------------- 1 | from ._element import _Element 2 | from ._module_misc import LxmlError 3 | from ._xmlerror import _ListErrorLog 4 | 5 | class XIncludeError(LxmlError): ... 6 | 7 | class XInclude: 8 | def __init__(self) -> None: ... 9 | @property 10 | def error_log(self) -> _ListErrorLog: ... 11 | def __call__(self, node: _Element) -> None: ... 12 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_xmlid.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Collection, Generic, Iterator, overload 3 | 4 | if sys.version_info >= (3, 11): 5 | from typing import Self 6 | else: 7 | from typing_extensions import Self 8 | 9 | from .._types import _ET, _AnyStr, _DefEtreeParsers, _FileReadSource 10 | from ._element import _Element, _ElementTree 11 | 12 | # arguments for these module funcs are the same as XML() and parse() 13 | 14 | @overload 15 | def XMLID( 16 | text: _AnyStr, 17 | parser: _DefEtreeParsers[_ET], 18 | *, 19 | base_url: _AnyStr | None = None, 20 | ) -> tuple[_ET, dict[str, _ET]]: ... 21 | @overload 22 | def XMLID( 23 | text: _AnyStr, 24 | parser: None = None, 25 | *, 26 | base_url: _AnyStr | None = None, 27 | ) -> tuple[_Element, dict[str, _Element]]: ... 28 | 29 | # It is interesting how _IDDict is used below but not above 30 | 31 | @overload 32 | def XMLDTDID( 33 | text: _AnyStr, 34 | parser: _DefEtreeParsers[_ET], 35 | *, 36 | base_url: _AnyStr | None = None, 37 | ) -> tuple[_ET, _IDDict[_ET]]: ... 38 | @overload 39 | def XMLDTDID( 40 | text: _AnyStr, 41 | parser: None = None, 42 | *, 43 | base_url: _AnyStr | None = None, 44 | ) -> tuple[_Element, _IDDict]: ... 45 | @overload 46 | def parseid( 47 | source: _FileReadSource, 48 | parser: _DefEtreeParsers[_ET], 49 | *, 50 | base_url: _AnyStr | None = None, 51 | ) -> tuple[_ElementTree[_ET], _IDDict[_ET]]: ... 52 | @overload 53 | def parseid( 54 | source: _FileReadSource, 55 | parser: None = None, 56 | *, 57 | base_url: _AnyStr | None = None, 58 | ) -> tuple[_ElementTree, _IDDict]: ... 59 | 60 | class _IDDict(Collection[str], Generic[_ET]): 61 | """Dictionary-like proxy class that mapps ID attributes to elements 62 | 63 | Original Docstring 64 | ------------------ 65 | The dictionary must be instantiated with the root element of a parsed XML 66 | document, otherwise the behaviour is undefined. Elements and XML trees 67 | that were created or modified 'by hand' are not supported. 68 | """ 69 | 70 | def __contains__(self, __o: object) -> bool: ... 71 | def __getitem__(self, __k: _AnyStr) -> _ET: ... 72 | def __iter__(self) -> Iterator[str]: ... 73 | def __len__(self) -> int: ... 74 | def copy(self) -> Self: ... 75 | def get(self, id_name: _AnyStr) -> _ET: ... 76 | def has_key(self, id_name: object) -> bool: ... 77 | def keys(self) -> list[str]: ... 78 | def iterkeys(self) -> Self: ... # WTF??? Must be nobody use this. 79 | def items(self) -> list[tuple[str, _ET]]: ... 80 | def iteritems(self) -> Iterator[tuple[str, _ET]]: ... 81 | def values(self) -> list[_ET]: ... 82 | def itervalues(self) -> Iterator[_ET]: ... 83 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_xmlschema.pyi: -------------------------------------------------------------------------------- 1 | from typing import overload 2 | 3 | from .._types import _ElementOrTree, _FileReadSource 4 | from ._module_misc import LxmlError, _Validator 5 | 6 | class XMLSchemaError(LxmlError): ... 7 | class XMLSchemaParseError(XMLSchemaError): ... 8 | class XMLSchemaValidateError(XMLSchemaError): ... 9 | 10 | class XMLSchema(_Validator): 11 | # file arg only useful when etree arg is None 12 | @overload 13 | def __init__( 14 | self, 15 | etree: _ElementOrTree, 16 | *, 17 | file: None = None, 18 | attribute_defaults: bool = False, 19 | ) -> None: ... 20 | @overload 21 | def __init__( 22 | self, 23 | etree: None = None, 24 | *, 25 | file: _FileReadSource, 26 | attribute_defaults: bool = False, 27 | ) -> None: ... 28 | def __call__(self, etree: _ElementOrTree) -> bool: ... 29 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/etree/_xpath.pyi: -------------------------------------------------------------------------------- 1 | # 2 | # Note that exception classes and funcs defined in 3 | # etree/_extension.pxi are merged here. 4 | # 5 | 6 | import sys 7 | from abc import abstractmethod 8 | from types import ModuleType 9 | from typing import Any, Callable, Collection, Generic, Protocol, final, overload 10 | 11 | if sys.version_info >= (3, 13): 12 | from warnings import deprecated 13 | else: 14 | from typing_extensions import deprecated 15 | 16 | from .._types import ( 17 | _ET, 18 | _AnyStr, 19 | _ElementOrTree, 20 | _NonDefaultNSMapArg, 21 | _XPathExtFuncArg, 22 | _XPathObject, 23 | _XPathVarArg, 24 | ) 25 | from ._element import _Element, _ElementTree 26 | from ._module_misc import LxmlError, LxmlSyntaxError 27 | from ._xmlerror import _ListErrorLog 28 | 29 | class XPathError(LxmlError): 30 | """Base class of all XPath errors""" 31 | 32 | class XPathEvalError(XPathError): 33 | """Error during XPath evaluation""" 34 | 35 | class XPathFunctionError(XPathEvalError): 36 | """Internal error looking up an XPath extension function""" 37 | 38 | class XPathResultError(XPathEvalError): 39 | """Error handling an XPath result""" 40 | 41 | class XPathSyntaxError(LxmlSyntaxError, XPathError): 42 | """Error in XPath expression""" 43 | 44 | class _XPathEvaluatorBase(Protocol): 45 | @property 46 | def error_log(self) -> _ListErrorLog: ... 47 | @abstractmethod 48 | def __call__(self, _arg: Any, /, **__var: _XPathVarArg) -> _XPathObject: ... 49 | # evaluate() should have been abstract like __call__(), but requiring all 50 | # subclasses to add deprecated method is idiocy 51 | @deprecated("Removed since 5.0; call instance directly instead") 52 | def evaluate(self, _arg: Any, /, **__var: _XPathVarArg) -> _XPathObject: ... 53 | 54 | class XPath(_XPathEvaluatorBase): 55 | def __init__( 56 | self, 57 | path: _AnyStr, 58 | *, 59 | namespaces: _NonDefaultNSMapArg | None = None, 60 | extensions: _XPathExtFuncArg | None = None, 61 | regexp: bool = True, 62 | smart_strings: bool = True, 63 | ) -> None: ... 64 | def __call__( 65 | self, _etree_or_element: _ElementOrTree, /, **_variables: _XPathVarArg 66 | ) -> _XPathObject: ... 67 | @property 68 | def path(self) -> str: ... 69 | 70 | class ETXPath(XPath): 71 | def __init__( 72 | self, 73 | path: _AnyStr, 74 | *, 75 | extensions: _XPathExtFuncArg | None = None, 76 | regexp: bool = True, 77 | smart_strings: bool = True, 78 | ) -> None: ... 79 | 80 | class XPathElementEvaluator(_XPathEvaluatorBase): 81 | def __init__( 82 | self, 83 | element: _Element, 84 | *, 85 | namespaces: _NonDefaultNSMapArg | None = None, 86 | extensions: _XPathExtFuncArg | None = None, 87 | regexp: bool = True, 88 | smart_strings: bool = True, 89 | ) -> None: ... 90 | def __call__( 91 | self, _path: _AnyStr, /, **_variables: _XPathVarArg 92 | ) -> _XPathObject: ... 93 | def register_namespace(self, prefix: _AnyStr, uri: _AnyStr) -> None: ... 94 | def register_namespaces(self, namespaces: _NonDefaultNSMapArg | None) -> None: ... 95 | 96 | class XPathDocumentEvaluator(XPathElementEvaluator): 97 | def __init__( 98 | self, 99 | etree: _ElementTree, 100 | *, 101 | namespaces: _NonDefaultNSMapArg | None = None, 102 | extensions: _XPathExtFuncArg | None = None, 103 | regexp: bool = True, 104 | smart_strings: bool = True, 105 | ) -> None: ... 106 | 107 | @overload 108 | def XPathEvaluator( 109 | etree_or_element: _Element, 110 | *, 111 | namespaces: _NonDefaultNSMapArg | None = None, 112 | extensions: _XPathExtFuncArg | None = None, 113 | regexp: bool = True, 114 | smart_strings: bool = True, 115 | ) -> XPathElementEvaluator: ... 116 | @overload 117 | def XPathEvaluator( 118 | etree_or_element: _ElementTree, 119 | *, 120 | namespaces: _NonDefaultNSMapArg | None = None, 121 | extensions: _XPathExtFuncArg | None = None, 122 | regexp: bool = True, 123 | smart_strings: bool = True, 124 | ) -> XPathDocumentEvaluator: ... 125 | @final 126 | class _ElementUnicodeResult(str, Generic[_ET]): 127 | """Smart string is a private str subclass documented in 128 | [return types](https://lxml.de/xpathxslt.html#xpath-return-values) 129 | of XPath evaluation result. 130 | 131 | Please [visit wiki page](https://github.com/abelcheung/types-lxml/wiki/Smart-string-usage) 132 | on description and how to use it in you code. 133 | """ 134 | 135 | @property 136 | def is_attribute(self) -> bool: ... 137 | @property 138 | def is_tail(self) -> bool: ... 139 | @property 140 | def is_text(self) -> bool: ... 141 | @property 142 | def attrname(self) -> str | None: ... 143 | def getparent(self: _ElementUnicodeResult[_ET]) -> _ET | None: ... 144 | 145 | @overload # no namespace 146 | def Extension( 147 | module: object | ModuleType, 148 | function_mapping: dict[str, str] | Collection[str] | None = None, 149 | *, 150 | ns: None = None, 151 | ) -> dict[tuple[None, str], Callable[..., Any]]: ... 152 | @overload # namespace present 153 | def Extension( 154 | module: object | ModuleType, 155 | function_mapping: dict[str, str] | Collection[str] | None = None, 156 | *, 157 | ns: str, 158 | ) -> dict[tuple[str, str], Callable[..., Any]]: 159 | """Build a dictionary of extension functions from the functions 160 | defined in a module or the methods of an object. 161 | 162 | Original Docstring 163 | ------------------ 164 | As second argument, you can pass an additional mapping of 165 | attribute names to XPath function names, or a list of function 166 | names that should be taken. 167 | 168 | The ``ns`` keyword argument accepts a namespace URI for the XPath 169 | functions. 170 | """ 171 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/__init__.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 11): 4 | from typing import LiteralString 5 | else: 6 | from typing_extensions import LiteralString 7 | 8 | from ._element import ( 9 | Classes as Classes, 10 | Element as Element, 11 | HtmlComment as HtmlComment, 12 | HtmlElement as HtmlElement, 13 | HtmlEntity as HtmlEntity, 14 | HtmlProcessingInstruction as HtmlProcessingInstruction, 15 | ) 16 | from ._form import ( 17 | CheckboxGroup as CheckboxGroup, 18 | CheckboxValues as CheckboxValues, 19 | FieldsDict as FieldsDict, 20 | FormElement as FormElement, 21 | InputElement as InputElement, 22 | InputGetter as InputGetter, 23 | LabelElement as LabelElement, 24 | MultipleSelectOptions as MultipleSelectOptions, 25 | RadioGroup as RadioGroup, 26 | SelectElement as SelectElement, 27 | TextareaElement as TextareaElement, 28 | submit_form as submit_form, 29 | ) 30 | from ._funcs import ( 31 | find_class as find_class, 32 | find_rel_links as find_rel_links, 33 | html_to_xhtml as html_to_xhtml, 34 | iterlinks as iterlinks, 35 | make_links_absolute as make_links_absolute, 36 | open_in_browser as open_in_browser, 37 | resolve_base_href as resolve_base_href, 38 | rewrite_links as rewrite_links, 39 | tostring as tostring, 40 | xhtml_to_html as xhtml_to_html, 41 | ) 42 | from ._parse import ( 43 | HtmlElementClassLookup as HtmlElementClassLookup, 44 | HTMLParser as HTMLParser, 45 | XHTMLParser as XHTMLParser, 46 | document_fromstring as document_fromstring, 47 | fragment_fromstring as fragment_fromstring, 48 | fragments_fromstring as fragments_fromstring, 49 | fromstring as fromstring, 50 | html_parser as html_parser, 51 | parse as parse, 52 | xhtml_parser as xhtml_parser, 53 | ) 54 | 55 | XHTML_NAMESPACE: LiteralString 56 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/_form.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import ( 3 | Any, 4 | Callable, 5 | Collection, 6 | Iterable, 7 | Iterator, 8 | Literal, 9 | MutableMapping, 10 | MutableSet, 11 | TypeVar, 12 | overload, 13 | ) 14 | 15 | if sys.version_info >= (3, 10): 16 | from typing import TypeAlias 17 | else: 18 | from typing_extensions import TypeAlias 19 | 20 | if sys.version_info >= (3, 11): 21 | from typing import Never 22 | else: 23 | from typing_extensions import Never 24 | 25 | from .._types import SupportsLaxedItems, _AnyStr 26 | from ._element import HtmlElement 27 | 28 | _T = TypeVar("_T") 29 | 30 | _FormValues: TypeAlias = list[tuple[str, str]] 31 | _AnyInputElement: TypeAlias = InputElement | SelectElement | TextareaElement 32 | 33 | class FormElement(HtmlElement): 34 | @property 35 | def inputs(self) -> InputGetter: ... 36 | @property 37 | def fields(self) -> FieldsDict: ... 38 | @fields.setter 39 | def fields(self, __v: SupportsLaxedItems[str, str]) -> None: ... 40 | action: str 41 | method: str 42 | def form_values(self) -> _FormValues: ... 43 | 44 | # FieldsDict is actually MutableMapping *sans* __delitem__ 45 | # However it is much simpler to keep MutableMapping and only 46 | # override __delitem__ 47 | class FieldsDict(MutableMapping[str, str]): 48 | inputs: InputGetter 49 | def __init__(self, inputs: InputGetter) -> None: ... 50 | def __getitem__(self, __k: str) -> str: ... 51 | def __setitem__(self, __k: str, __v: str) -> None: ... 52 | # Use Never for argument to issue early warning that 53 | # __delitem__ can't be used 54 | def __delitem__(self, __k: Never) -> Never: ... # type: ignore[override] 55 | def __iter__(self) -> Iterator[str]: ... 56 | def __len__(self) -> int: ... 57 | 58 | # Quoting from source: it's unclear if this is a dictionary-like object 59 | # or list-like object 60 | class InputGetter(Collection[_AnyInputElement]): 61 | form: FormElement 62 | def __init__(self, form: FormElement) -> None: ... 63 | # __getitem__ is special here: for checkbox group and radio group, 64 | # it returns special list-like object instead of HtmlElement 65 | def __getitem__( 66 | self, __k: str 67 | ) -> _AnyInputElement | RadioGroup | CheckboxGroup: ... 68 | def keys(self) -> list[str]: ... 69 | def items( 70 | self, 71 | ) -> list[tuple[str, _AnyInputElement | RadioGroup | CheckboxGroup]]: ... 72 | def __contains__(self, __o: object) -> bool: ... 73 | def __iter__(self) -> Iterator[_AnyInputElement]: ... 74 | def __len__(self) -> int: ... 75 | 76 | class _InputMixin: 77 | @property 78 | def name(self) -> str | None: ... 79 | @name.setter 80 | def name(self, __v: _AnyStr | None) -> None: ... 81 | 82 | class TextareaElement(_InputMixin, HtmlElement): 83 | value: str 84 | 85 | class SelectElement(_InputMixin, HtmlElement): 86 | multiple: bool 87 | @property 88 | def value(self) -> str | MultipleSelectOptions: ... 89 | @value.setter 90 | def value(self, value: _AnyStr | Collection[str]) -> None: ... 91 | @property 92 | def value_options(self) -> list[str]: ... 93 | 94 | class MultipleSelectOptions(MutableSet[str]): 95 | select: SelectElement 96 | def __init__(self, select: SelectElement) -> None: ... 97 | @property 98 | def options(self) -> Iterator[HtmlElement]: ... 99 | def __contains__(self, x: object) -> bool: ... 100 | def __iter__(self) -> Iterator[str]: ... 101 | def __len__(self) -> int: ... 102 | def add( # pyright: ignore[reportIncompatibleMethodOverride] 103 | self, item: str 104 | ) -> None: ... 105 | def remove( # pyright: ignore[reportIncompatibleMethodOverride] 106 | self, item: str 107 | ) -> None: ... 108 | def discard( # pyright: ignore[reportIncompatibleMethodOverride] 109 | self, item: str 110 | ) -> None: ... 111 | 112 | class RadioGroup(list[InputElement]): 113 | value: str | None 114 | @property 115 | def value_options(self) -> list[str]: ... 116 | 117 | class CheckboxGroup(list[InputElement]): 118 | @property 119 | def value(self) -> CheckboxValues: ... 120 | @value.setter 121 | def value(self, __v: Iterable[str]) -> None: ... 122 | @property 123 | def value_options(self) -> list[str]: ... 124 | 125 | class CheckboxValues(MutableSet[str]): 126 | group: CheckboxGroup 127 | def __init__(self, group: CheckboxGroup) -> None: ... 128 | def __contains__(self, x: object) -> bool: ... 129 | def __iter__(self) -> Iterator[str]: ... 130 | def __len__(self) -> int: ... 131 | def add(self, value: str) -> None: ... 132 | def discard( # pyright: ignore[reportIncompatibleMethodOverride] 133 | self, item: str 134 | ) -> None: ... 135 | 136 | class InputElement(_InputMixin, HtmlElement): 137 | type: str 138 | value: str | None 139 | checked: bool 140 | @property 141 | def checkable(self) -> bool: ... 142 | 143 | class LabelElement(HtmlElement): 144 | @property 145 | def for_element(self) -> HtmlElement | None: ... 146 | @for_element.setter 147 | def for_element(self, __v: HtmlElement) -> None: ... 148 | 149 | # open_http argument has signature (method, url, values) -> Any 150 | @overload 151 | def submit_form( 152 | form: FormElement, 153 | extra_values: _FormValues | SupportsLaxedItems[str, str] | None = None, 154 | open_http: None = None, 155 | ) -> Any: ... # See typeshed _UrlOpenRet 156 | @overload # open_http as positional argument 157 | def submit_form( 158 | form: FormElement, 159 | extra_values: _FormValues | SupportsLaxedItems[str, str] | None, 160 | open_http: Callable[[Literal["GET", "POST"], str, _FormValues], _T], 161 | ) -> _T: ... 162 | @overload # open_http as keyword argument 163 | def submit_form( 164 | form: FormElement, 165 | extra_values: _FormValues | SupportsLaxedItems[str, str] | None = None, 166 | *, 167 | open_http: Callable[[Literal["GET", "POST"], str, _FormValues], _T], 168 | ) -> _T: ... 169 | 170 | # No need to annotate open_http_urllib. 171 | # Only intended as callback func object in submit_form() argument, 172 | # and already used as default if open_http argument is absent. 173 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/_funcs.pyi: -------------------------------------------------------------------------------- 1 | from typing import AnyStr, Callable, Iterator, Literal, TypeVar, overload 2 | 3 | from .._types import _AnyStr, _ElementOrTree, _OutputMethodArg 4 | from ._element import _HANDLE_FAILURES, HtmlElement 5 | 6 | _HtmlDoc_T = TypeVar("_HtmlDoc_T", str, bytes, HtmlElement) 7 | 8 | # These are HtmlMixin methods converted to standard functions, 9 | # with element or HTML string as first argument followed by all 10 | # pre-existing args. Quoting from source: 11 | # 12 | # ... the function takes either an element or an HTML string. It 13 | # returns whatever the function normally returns, or if the function 14 | # works in-place (and so returns None) it returns a serialized form 15 | # of the resulting document. 16 | # 17 | # Special Notes: 18 | # 1. These funcs operate on attributes that only make sense on 19 | # normal HtmlElements; lxml raises exception otherwise. 20 | # 2. Although extra 'copy' argument is available, it is intended 21 | # only for internal use by each function, not something to be 22 | # arbitrarily changed by users, thus not available in stub. 23 | # 24 | # HACK Interesting, a 15+ yrs bug remains undiscovered, 25 | # probably nobody is using them at all? 26 | # All these standalone link funcs make use of _MethodFunc 27 | # internal class in html/__init__.py, which has bug when 28 | # converting input data. If input is not Element, the class 29 | # automatically converts input to Element via fromstring(), 30 | # taking in all keyword args used in link function call. 31 | # Many of these keywords are unknown to fromstring(), 32 | # thus causing Exception. Workaround this using @overload. 33 | 34 | @overload 35 | def find_rel_links( 36 | doc: HtmlElement, 37 | rel: str, 38 | ) -> list[HtmlElement]: ... 39 | @overload 40 | def find_rel_links( 41 | doc: _AnyStr, 42 | rel: str, 43 | /, 44 | ) -> list[HtmlElement]: ... 45 | @overload 46 | def find_class( 47 | doc: HtmlElement, 48 | class_name: _AnyStr, 49 | ) -> list[HtmlElement]: ... 50 | @overload 51 | def find_class( 52 | doc: _AnyStr, 53 | class_name: _AnyStr, 54 | /, 55 | ) -> list[HtmlElement]: ... 56 | @overload 57 | def make_links_absolute( 58 | doc: HtmlElement, 59 | base_url: str | None = None, 60 | resolve_base_href: bool = True, 61 | handle_failures: _HANDLE_FAILURES | None = None, 62 | ) -> HtmlElement: ... 63 | @overload 64 | def make_links_absolute( 65 | doc: AnyStr, 66 | base_url: str | None = None, 67 | resolve_base_href: bool = True, 68 | handle_failures: _HANDLE_FAILURES | None = None, 69 | /, 70 | ) -> AnyStr: ... 71 | @overload 72 | def resolve_base_href( 73 | doc: HtmlElement, 74 | handle_failures: _HANDLE_FAILURES | None = None, 75 | ) -> HtmlElement: ... 76 | @overload 77 | def resolve_base_href( 78 | doc: AnyStr, 79 | handle_failures: _HANDLE_FAILURES | None = None, 80 | /, 81 | ) -> AnyStr: ... 82 | def iterlinks( 83 | doc: _HtmlDoc_T, 84 | ) -> Iterator[tuple[HtmlElement, str | None, str, int]]: ... 85 | @overload 86 | def rewrite_links( 87 | doc: HtmlElement, 88 | link_repl_func: Callable[[str], str | None], 89 | resolve_base_href: bool = True, 90 | base_href: str | None = None, 91 | ) -> HtmlElement: ... 92 | @overload 93 | def rewrite_links( 94 | doc: AnyStr, 95 | link_repl_func: Callable[[str], str | None], 96 | resolve_base_href: bool = True, 97 | base_href: str | None = None, 98 | /, 99 | ) -> AnyStr: ... 100 | 101 | # 102 | # Tree conversion 103 | # 104 | def html_to_xhtml(html: _ElementOrTree[HtmlElement]) -> None: ... 105 | def xhtml_to_html(xhtml: _ElementOrTree[HtmlElement]) -> None: ... 106 | 107 | # 108 | # Tree output 109 | # 110 | # 1. Encoding issue is similar to etree.tostring(). 111 | # 112 | # 2. Unlike etree.tostring(), all arguments here are not explicitly 113 | # keyword-only. Using overload with no default value would be 114 | # impossible, as the two arguments before it has default value. 115 | # Need to make a choice here: enforce all arguments to be keyword-only. 116 | # Less liberal code, but easier to maintain in long term for users. 117 | # 118 | # 3. Although html.tostring() does not forbid method="c14n" (or c14n2), 119 | # calling tostring() this way would render almost all keyword arguments 120 | # useless, defeating the purpose of existence of html.tostring(). 121 | # Besides, no c14n specific arguments are accepted here, so it is 122 | # better to let etree.tostring() handle C14N. 123 | @overload # encoding=str / "unicode" 124 | def tostring( # type: ignore[overload-overlap] 125 | doc: _ElementOrTree[HtmlElement], 126 | *, 127 | pretty_print: bool = False, 128 | include_meta_content_type: bool = False, 129 | encoding: type[str] | Literal["unicode"], 130 | method: _OutputMethodArg = "html", 131 | with_tail: bool = True, 132 | doctype: str | None = None, 133 | ) -> str: ... 134 | @overload # encoding="..." / None, no encoding arg 135 | def tostring( 136 | doc: _ElementOrTree[HtmlElement], 137 | *, 138 | pretty_print: bool = False, 139 | include_meta_content_type: bool = False, 140 | encoding: str | None = None, 141 | method: _OutputMethodArg = "html", 142 | with_tail: bool = True, 143 | doctype: str | None = None, 144 | ) -> bytes: ... 145 | 146 | # 147 | # Debug 148 | # 149 | def open_in_browser( 150 | doc: _ElementOrTree[HtmlElement], encoding: str | type[str] | None = None 151 | ) -> None: ... 152 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/_parse.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any, Iterable, Literal, MutableMapping, overload 3 | 4 | if sys.version_info >= (3, 10): 5 | from typing import TypeAlias 6 | else: 7 | from typing_extensions import TypeAlias 8 | 9 | from .. import etree 10 | from .._types import ( 11 | Unused, 12 | _AnyStr, 13 | _DefEtreeParsers, 14 | _ElemClsLookupArg, 15 | _FileReadSource, 16 | ) 17 | from ._element import HtmlElement 18 | 19 | _HtmlElemParser: TypeAlias = _DefEtreeParsers[HtmlElement] 20 | 21 | # 22 | # Parser 23 | # 24 | 25 | # Stub version before March 2023 used to omit 'target' parameter, which 26 | # would nullify default HTML element lookup behavior, degenerating html 27 | # submodule parsers into etree ones. Since it is decided to not support 28 | # custom target parser for now, we just use superclass constructor for 29 | # coherence. Same for XHTMLParser below. 30 | class HTMLParser(etree.HTMLParser[HtmlElement]): 31 | """An HTML parser configured to return ``lxml.html`` Element 32 | objects. 33 | 34 | Notes 35 | ----- 36 | This subclass is not specialized, unlike the ``etree`` counterpart. 37 | They are designed to always handle ``HtmlElement``; 38 | for generating other kinds of ``_Elements``, one should use 39 | etree parsers with ``set_element_class_lookup()`` method instead. 40 | In that case, see ``_FeedParser.set_element_class_lookup()`` for more info. 41 | """ 42 | 43 | @property 44 | def target(self) -> None: ... 45 | 46 | class XHTMLParser(etree.XMLParser[HtmlElement]): 47 | """An XML parser configured to return ``lxml.html`` Element 48 | objects. 49 | 50 | Annotation 51 | ---------- 52 | This subclass is not specialized, unlike the ``etree`` counterpart. 53 | They are designed to always handle ``HtmlElement``; 54 | for generating other kinds of ``_Elements``, one should use 55 | etree parsers with ``set_element_class_lookup()`` method instead. 56 | In that case, see ``_FeedParser.set_element_class_lookup()`` for more info. 57 | 58 | Original doc 59 | ------------ 60 | Note that this parser is not really XHTML aware unless you let it 61 | load a DTD that declares the HTML entities. To do this, make sure 62 | you have the XHTML DTDs installed in your catalogs, and create the 63 | parser like this:: 64 | 65 | >>> parser = XHTMLParser(load_dtd=True) 66 | 67 | If you additionally want to validate the document, use this:: 68 | 69 | >>> parser = XHTMLParser(dtd_validation=True) 70 | 71 | For catalog support, see http://www.xmlsoft.org/catalog.html. 72 | """ 73 | 74 | @property 75 | def target(self) -> None: ... 76 | 77 | html_parser: HTMLParser 78 | xhtml_parser: XHTMLParser 79 | 80 | # 81 | # Parsing funcs 82 | # 83 | 84 | # Calls etree.fromstring(html, parser, **kw) which has signature 85 | # fromstring(text, parser, *, base_url) 86 | def document_fromstring( 87 | html: _AnyStr, 88 | parser: _HtmlElemParser | None = None, 89 | ensure_head_body: bool = False, 90 | *, 91 | base_url: str | None = None, 92 | ) -> HtmlElement: ... 93 | @overload 94 | def fragments_fromstring( # type: ignore[overload-overlap] 95 | html: _AnyStr, 96 | no_leading_text: Literal[True], 97 | base_url: str | None = None, 98 | parser: _HtmlElemParser | None = None, 99 | ) -> list[HtmlElement]: ... 100 | @overload 101 | def fragments_fromstring( 102 | html: _AnyStr, 103 | no_leading_text: bool = False, 104 | base_url: str | None = None, 105 | parser: _HtmlElemParser | None = None, 106 | ) -> list[str | HtmlElement]: ... 107 | def fragment_fromstring( 108 | html: _AnyStr, 109 | create_parent: bool = False, 110 | base_url: str | None = None, 111 | parser: _HtmlElemParser | None = None, 112 | ) -> HtmlElement: ... 113 | def fromstring( 114 | html: _AnyStr, 115 | base_url: str | None = None, 116 | parser: _HtmlElemParser | None = None, 117 | ) -> HtmlElement: ... 118 | def parse( 119 | filename_or_url: _FileReadSource, 120 | parser: _HtmlElemParser | None = None, 121 | base_url: str | None = None, 122 | ) -> etree._ElementTree[HtmlElement]: ... 123 | 124 | # 125 | # Element Lookup 126 | # 127 | 128 | class HtmlElementClassLookup(etree.CustomElementClassLookup): 129 | def __init__( 130 | self, 131 | # Should have been something like Mapping[str, type[HtmlElement]], 132 | # but unfortunately classes mapping is required to be mutable 133 | classes: MutableMapping[str, Any] | None = None, 134 | # docstring says mixins is mapping, but implementation says otherwise 135 | mixins: Iterable[tuple[str, type[HtmlElement]]] | None = None, 136 | ) -> None: ... 137 | def lookup( # pyright: ignore[reportIncompatibleMethodOverride] 138 | self, 139 | node_type: _ElemClsLookupArg | None, 140 | document: Unused, 141 | namespace: Unused, 142 | name: str, # type: ignore[override] 143 | ) -> type[HtmlElement] | None: ... 144 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/builder.pyi: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from ..builder import ElementMaker 4 | from ._element import HtmlElement 5 | from ._form import ( 6 | FormElement, 7 | InputElement, 8 | LabelElement, 9 | SelectElement, 10 | TextareaElement, 11 | ) 12 | 13 | E: ElementMaker[HtmlElement] 14 | 15 | # Use inferred type, value is not important in stub 16 | A = E.a 17 | ABBR = E.abbr 18 | ACRONYM = E.acronym 19 | ADDRESS = E.address 20 | APPLET = E.applet 21 | AREA = E.area 22 | B = E.b 23 | BASE = E.base 24 | BASEFONT = E.basefont 25 | BDO = E.bdo 26 | BIG = E.big 27 | BLOCKQUOTE = E.blockquote 28 | BODY = E.body 29 | BR = E.br 30 | BUTTON = E.button 31 | CAPTION = E.caption 32 | CENTER = E.center 33 | CITE = E.cite 34 | CODE = E.code 35 | COL = E.col 36 | COLGROUP = E.colgroup 37 | DD = E.dd 38 | DEL = E.__getattr__("del") 39 | DFN = E.dfn 40 | DIR = E.dir 41 | DIV = E.div 42 | DL = E.dl 43 | DT = E.dt 44 | EM = E.em 45 | FIELDSET = E.fieldset 46 | FONT = E.font 47 | FORM: partial[FormElement] 48 | FRAME = E.frame 49 | FRAMESET = E.frameset 50 | H1 = E.h1 51 | H2 = E.h2 52 | H3 = E.h3 53 | H4 = E.h4 54 | H5 = E.h5 55 | H6 = E.h6 56 | HEAD = E.head 57 | HR = E.hr 58 | HTML = E.html 59 | I = E.i 60 | IFRAME = E.iframe 61 | IMG = E.img 62 | INPUT: partial[InputElement] 63 | INS = E.ins 64 | ISINDEX = E.isindex 65 | KBD = E.kbd 66 | LABEL: partial[LabelElement] 67 | LEGEND = E.legend 68 | LI = E.li 69 | LINK = E.link 70 | MAP = E.map 71 | MENU = E.menu 72 | META = E.meta 73 | NOFRAMES = E.noframes 74 | NOSCRIPT = E.noscript 75 | OBJECT = E.object 76 | OL = E.ol 77 | OPTGROUP = E.optgroup 78 | OPTION = E.option 79 | P = E.p 80 | PARAM = E.param 81 | PRE = E.pre 82 | Q = E.q 83 | S = E.s 84 | SAMP = E.samp 85 | SCRIPT = E.script 86 | SELECT: partial[SelectElement] 87 | SMALL = E.small 88 | SPAN = E.span 89 | STRIKE = E.strike 90 | STRONG = E.strong 91 | STYLE = E.style 92 | SUB = E.sub 93 | SUP = E.sup 94 | TABLE = E.table 95 | TBODY = E.tbody 96 | TD = E.td 97 | TEXTAREA: partial[TextareaElement] 98 | TFOOT = E.tfoot 99 | TH = E.th 100 | THEAD = E.thead 101 | TITLE = E.title 102 | TR = E.tr 103 | TT = E.tt 104 | U = E.u 105 | UL = E.ul 106 | VAR = E.var 107 | 108 | # attributes 109 | ATTR = dict 110 | 111 | def CLASS(v: str) -> dict[str, str]: ... 112 | def FOR(v: str) -> dict[str, str]: ... 113 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/clean.pyi: -------------------------------------------------------------------------------- 1 | # 2 | # lxml.html.clean was removed from lxml proper since 5.2.0, 3 | # and extracted into its own project. 4 | # https://github.com/fedora-python/lxml_html_clean/ 5 | # 6 | # Although this part of stub is merged into lxml_html_clean 7 | # project, it will be kept here for a while for compatibility, 8 | # until most people migrate to newer lxml versions. 9 | # 10 | # Some arguments comes with a complex or longish default 11 | # values, it is better to look up API doc or source directly 12 | # 13 | 14 | import sys 15 | from typing import Collection, Iterable, Literal, Pattern, TypeVar, overload 16 | 17 | from .._types import _ElementOrTree 18 | from ..etree import _ElementTree 19 | from . import HtmlElement 20 | from ._funcs import _HtmlDoc_T 21 | 22 | if sys.version_info >= (3, 13): 23 | from warnings import deprecated 24 | else: 25 | from typing_extensions import deprecated 26 | 27 | # Similar to _funcs._HtmlDoc_T, but also supports ET; only used in Cleaner 28 | _DT = TypeVar("_DT", str, bytes, HtmlElement, _ElementTree[HtmlElement]) 29 | 30 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 31 | class Cleaner: 32 | @overload # if allow_tags present, remove_unknown_tags must be False 33 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 34 | def __init__( 35 | self, 36 | *, 37 | scripts: bool = True, 38 | javascript: bool = True, 39 | comments: bool = True, 40 | style: bool = False, 41 | inline_style: bool | None = None, 42 | links: bool = True, 43 | meta: bool = True, 44 | page_structure: bool = True, 45 | processing_instructions: bool = True, 46 | embedded: bool = True, 47 | frames: bool = True, 48 | forms: bool = True, 49 | annoying_tags: bool = True, 50 | remove_tags: Collection[str] = (), 51 | allow_tags: Collection[str] = (), 52 | kill_tags: Collection[str] = (), 53 | remove_unknown_tags: Literal[False] = False, 54 | safe_attrs_only: bool = True, 55 | safe_attrs: Collection[str] = ..., # keep ellipsis 56 | add_nofollow: bool = False, 57 | host_whitelist: Collection[str] = (), 58 | whitelist_tags: Collection[str] | None = {"iframe", "embed"}, 59 | ) -> None: ... 60 | @overload # ... otherwise allow_tags arg must not exist 61 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 62 | def __init__( 63 | self, 64 | *, 65 | scripts: bool = True, 66 | javascript: bool = True, 67 | comments: bool = True, 68 | style: bool = False, 69 | inline_style: bool | None = None, 70 | links: bool = True, 71 | meta: bool = True, 72 | page_structure: bool = True, 73 | processing_instructions: bool = True, 74 | embedded: bool = True, 75 | frames: bool = True, 76 | forms: bool = True, 77 | annoying_tags: bool = True, 78 | remove_tags: Collection[str] = (), 79 | kill_tags: Collection[str] = (), 80 | remove_unknown_tags: bool = True, 81 | safe_attrs_only: bool = True, 82 | safe_attrs: Collection[str] = ..., # keep ellipsis 83 | add_nofollow: bool = False, 84 | host_whitelist: Collection[str] = (), 85 | whitelist_tags: Collection[str] = {"iframe", "embed"}, 86 | ) -> None: ... 87 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 88 | def __call__(self, doc: _ElementOrTree[HtmlElement]) -> None: ... 89 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 90 | def allow_follow(self, anchor: HtmlElement) -> bool: ... 91 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 92 | def allow_element(self, el: HtmlElement) -> bool: ... 93 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 94 | def allow_embedded_url(self, el: HtmlElement, url: str) -> bool: ... 95 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 96 | def kill_conditional_comments(self, doc: _ElementOrTree[HtmlElement]) -> None: ... 97 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 98 | def clean_html(self, html: _DT) -> _DT: ... 99 | 100 | clean: Cleaner # pyright: ignore[reportDeprecated] 101 | clean_html = clean.clean_html # pyright: ignore[reportDeprecated] 102 | 103 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 104 | def autolink( 105 | el: HtmlElement, 106 | link_regexes: Iterable[Pattern[str]] = ..., # keep ellipsis 107 | avoid_elements: Collection[str] = ..., # keep ellipsis 108 | avoid_hosts: Iterable[Pattern[str]] = ..., # keep ellipsis 109 | avoid_classes: Collection[str] = ["nolink"], 110 | ) -> None: ... 111 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 112 | def autolink_html( 113 | html: _HtmlDoc_T, 114 | link_regexes: Iterable[Pattern[str]] = ..., # keep ellipsis 115 | avoid_elements: Collection[str] = ..., # keep ellipsis 116 | avoid_hosts: Iterable[Pattern[str]] = ..., # keep ellipsis 117 | avoid_classes: Collection[str] = ["nolink"], 118 | ) -> _HtmlDoc_T: ... 119 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 120 | def word_break( 121 | el: HtmlElement, 122 | max_width: int = 40, 123 | avoid_elements: Collection[str] = ["pre", "textarea", "code"], 124 | avoid_classes: Collection[str] = ["nobreak"], 125 | break_character: str = chr(0x200B), 126 | ) -> None: ... 127 | @deprecated("Removed from lxml 5.2.0; use lxml_html_clean project instead") 128 | def word_break_html( 129 | html: _HtmlDoc_T, 130 | max_width: int = 40, 131 | avoid_elements: Collection[str] = ["pre", "textarea", "code"], 132 | avoid_classes: Collection[str] = ["nobreak"], 133 | break_character: str = chr(0x200B), 134 | ) -> _HtmlDoc_T: ... 135 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/defs.pyi: -------------------------------------------------------------------------------- 1 | empty_tags: frozenset[str] 2 | deprecated_tags: frozenset[str] 3 | link_attrs: frozenset[str] 4 | event_attrs: frozenset[str] 5 | safe_attrs: frozenset[str] 6 | top_level_tags: frozenset[str] 7 | head_tags: frozenset[str] 8 | general_block_tags: frozenset[str] 9 | list_tags: frozenset[str] 10 | table_tags: frozenset[str] 11 | block_tags: frozenset[str] 12 | form_tags: frozenset[str] 13 | special_inline_tags: frozenset[str] 14 | phrase_tags: frozenset[str] 15 | font_style_tags: frozenset[str] 16 | frame_tags: frozenset[str] 17 | html5_tags: frozenset[str] 18 | nonstandard_tags: frozenset[str] 19 | tags: frozenset[str] 20 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/diff.pyi: -------------------------------------------------------------------------------- 1 | from typing import Callable, Iterable, TypeVar 2 | 3 | from .._types import _AnyStr 4 | from ..etree import _Element 5 | 6 | _T = TypeVar("_T") 7 | 8 | # annotation attribute can be anything, which is stringified 9 | # later on; but the type would better be consistent though 10 | def html_annotate( 11 | doclist: Iterable[tuple[str, _T]], 12 | markup: Callable[[str, _T], str] = ..., # keep ellipsis 13 | ) -> str: ... 14 | def htmldiff( 15 | old_html: _Element | _AnyStr, 16 | new_html: _Element | _AnyStr, 17 | ) -> str: ... 18 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/html5parser.pyi: -------------------------------------------------------------------------------- 1 | # 2 | # Note that this interface only generates lxml.etree Elements, not lxml.html ones 3 | # See https://github.com/html5lib/html5lib-python/issues/102 4 | # 5 | 6 | from typing import Literal, overload 7 | 8 | import html5lib as _html5lib 9 | from _typeshed import SupportsRead 10 | 11 | from .._types import _AnyStr 12 | from ..etree import _Element, _ElementTree 13 | 14 | # Note that tree arg is dropped, because the sole purpose of using 15 | # this parser is to generate lxml element tree with html5lib parser. 16 | # Other arguments good for html5lib >= 1.0 17 | class HTMLParser(_html5lib.HTMLParser): 18 | def __init__( 19 | self, 20 | strict: bool = False, 21 | namespaceHTMLElements: bool = True, 22 | debug: bool = False, 23 | ) -> None: ... 24 | 25 | html_parser: HTMLParser 26 | 27 | # Notes: 28 | # - No XHTMLParser here. Lxml tries to probe for some hypothetical 29 | # XHTMLParser class in html5lib which had never existed. 30 | # The core probing logic of this user-contributed submodule has never 31 | # changed since last modification at 2010. Probably yet another 32 | # member of code wasteland. 33 | # - Exception raised when html= and guess_charset=True 34 | # are used together. This is due to flawed argument passing 35 | # into html5lib. We cover it up with @overload's 36 | # - Although other types of parser _might_ be usable (after implementing 37 | # parse() method, that is), such usage completely defeats the purpose of 38 | # creating this submodule. It is intended for subclassing or 39 | # init argument tweaking instead. 40 | 41 | @overload 42 | def document_fromstring( 43 | html: bytes, 44 | guess_charset: bool | None = None, 45 | parser: HTMLParser | None = None, 46 | ) -> _Element: ... 47 | @overload 48 | def document_fromstring( 49 | html: str, 50 | guess_charset: None = None, 51 | parser: HTMLParser | None = None, 52 | ) -> _Element: ... 53 | 54 | # 4 overloads for fragments_fromstring: 55 | # 2 for html (bytes/str) 56 | # 2 for no_leading_text (true/false) 57 | @overload # html=bytes, no_leading_text=true 58 | def fragments_fromstring( # type: ignore[overload-overlap] 59 | html: bytes, 60 | no_leading_text: Literal[True], 61 | guess_charset: bool | None = None, 62 | parser: HTMLParser | None = None, 63 | ) -> list[_Element]: ... 64 | @overload # html=str, no_leading_text=true 65 | def fragments_fromstring( # type: ignore[overload-overlap] 66 | html: str, 67 | no_leading_text: Literal[True], 68 | guess_charset: None = None, 69 | parser: HTMLParser | None = None, 70 | ) -> list[_Element]: ... 71 | @overload # html=bytes, no_leading_text=all cases 72 | def fragments_fromstring( 73 | html: bytes, 74 | no_leading_text: bool = False, 75 | guess_charset: bool | None = None, 76 | parser: HTMLParser | None = None, 77 | ) -> list[str | _Element]: ... 78 | @overload # html=str, no_leading_text=all cases 79 | def fragments_fromstring( 80 | html: str, 81 | no_leading_text: bool = False, 82 | guess_charset: None = None, 83 | parser: HTMLParser | None = None, 84 | ) -> list[str | _Element]: ... 85 | @overload 86 | def fragment_fromstring( 87 | html: str, 88 | create_parent: bool | _AnyStr = False, 89 | guess_charset: None = None, 90 | parser: HTMLParser | None = None, 91 | ) -> _Element: ... 92 | @overload 93 | def fragment_fromstring( 94 | html: bytes, 95 | create_parent: bool | _AnyStr = False, 96 | guess_charset: bool | None = None, 97 | parser: HTMLParser | None = None, 98 | ) -> _Element: ... 99 | @overload 100 | def fromstring( 101 | html: str, 102 | guess_charset: None = None, 103 | parser: HTMLParser | None = None, 104 | ) -> _Element: ... 105 | @overload 106 | def fromstring( 107 | html: bytes, 108 | guess_charset: bool | None = None, 109 | parser: HTMLParser | None = None, 110 | ) -> _Element: ... 111 | 112 | # html5lib doesn't support pathlib 113 | @overload 114 | def parse( 115 | filename_url_or_file: str | SupportsRead[str], 116 | guess_charset: None = None, 117 | parser: HTMLParser | None = None, 118 | ) -> _ElementTree: ... 119 | @overload 120 | def parse( 121 | filename_url_or_file: bytes | SupportsRead[bytes], 122 | guess_charset: bool | None = None, 123 | parser: HTMLParser | None = None, 124 | ) -> _ElementTree: ... 125 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/html/soupparser.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence, overload 2 | 3 | from _typeshed import SupportsRead 4 | from bs4 import BeautifulSoup, PageElement, SoupStrainer 5 | from bs4.builder import TreeBuilder 6 | 7 | from .._types import _ET, _AnyStr, _ElementFactory 8 | from ..etree import _ElementTree 9 | from . import HtmlElement 10 | 11 | # NOTES: 12 | # - kw only arguments for fromstring() and parse() are 13 | # taken from types-beautifulsoup4 14 | # - annotation for 'features' argument should have been 15 | # 16 | # features: str | Sequence[str] | None = None 17 | # 18 | # but current modification is much more helpful for users 19 | # - makeelement argument provides very exotic feature: 20 | # it's actually possible to convert BeautifulSoup html tree 21 | # into lxml XML element tree, not just lxml html tree 22 | 23 | @overload # makeelement is positional 24 | def fromstring( 25 | data: _AnyStr | SupportsRead[str] | SupportsRead[bytes], 26 | beautifulsoup: type[BeautifulSoup] | None, 27 | makeelement: _ElementFactory[_ET], 28 | *, 29 | features: str | Sequence[str] = "html.parser", 30 | builder: TreeBuilder | type[TreeBuilder] | None = None, 31 | parse_only: SoupStrainer | None = None, 32 | from_encoding: str | None = None, 33 | exclude_encodings: Sequence[str] | None = None, 34 | element_classes: dict[type[PageElement], type[Any]] | None = None, 35 | ) -> _ET: ... 36 | @overload # makeelement is kw 37 | def fromstring( 38 | data: _AnyStr | SupportsRead[str] | SupportsRead[bytes], 39 | beautifulsoup: type[BeautifulSoup] | None = None, 40 | *, 41 | makeelement: _ElementFactory[_ET], 42 | features: str | Sequence[str] = "html.parser", 43 | builder: TreeBuilder | type[TreeBuilder] | None = None, 44 | parse_only: SoupStrainer | None = None, 45 | from_encoding: str | None = None, 46 | exclude_encodings: Sequence[str] | None = None, 47 | element_classes: dict[type[PageElement], type[Any]] | None = None, 48 | ) -> _ET: ... 49 | @overload # makeelement not provided or is default 50 | def fromstring( 51 | data: _AnyStr | SupportsRead[str] | SupportsRead[bytes], 52 | beautifulsoup: type[BeautifulSoup] | None = None, 53 | makeelement: None = None, 54 | *, 55 | features: str | Sequence[str] = "html.parser", 56 | builder: TreeBuilder | type[TreeBuilder] | None = None, 57 | parse_only: SoupStrainer | None = None, 58 | from_encoding: str | None = None, 59 | exclude_encodings: Sequence[str] | None = None, 60 | element_classes: dict[type[PageElement], type[Any]] | None = None, 61 | ) -> HtmlElement: ... 62 | 63 | # Technically Path is also accepted for parse() file argument 64 | # but emits visible warning 65 | @overload # makeelement is positional 66 | def parse( 67 | file: _AnyStr | SupportsRead[str] | SupportsRead[bytes], 68 | beautifulsoup: type[BeautifulSoup] | None, 69 | makeelement: _ElementFactory[_ET], 70 | *, 71 | features: str | Sequence[str] = "html.parser", 72 | builder: TreeBuilder | type[TreeBuilder] | None = None, 73 | parse_only: SoupStrainer | None = None, 74 | from_encoding: str | None = None, 75 | exclude_encodings: Sequence[str] | None = None, 76 | element_classes: dict[type[PageElement], type[Any]] | None = None, 77 | ) -> _ElementTree[_ET]: ... 78 | @overload 79 | def parse( # makeelement is kw 80 | file: _AnyStr | SupportsRead[str] | SupportsRead[bytes], 81 | beautifulsoup: type[BeautifulSoup] | None = None, 82 | *, 83 | makeelement: _ElementFactory[_ET], 84 | features: str | Sequence[str] = "html.parser", 85 | builder: TreeBuilder | type[TreeBuilder] | None = None, 86 | parse_only: SoupStrainer | None = None, 87 | from_encoding: str | None = None, 88 | exclude_encodings: Sequence[str] | None = None, 89 | element_classes: dict[type[PageElement], type[Any]] | None = None, 90 | ) -> _ElementTree[_ET]: ... 91 | @overload # makeelement not provided or is default 92 | def parse( 93 | file: _AnyStr | SupportsRead[str] | SupportsRead[bytes], 94 | beautifulsoup: type[BeautifulSoup] | None = None, 95 | makeelement: None = None, 96 | *, 97 | features: str | Sequence[str] = "html.parser", 98 | builder: TreeBuilder | type[TreeBuilder] | None = None, 99 | parse_only: SoupStrainer | None = None, 100 | from_encoding: str | None = None, 101 | exclude_encodings: Sequence[str] | None = None, 102 | element_classes: dict[type[PageElement], type[Any]] | None = None, 103 | ) -> _ElementTree[HtmlElement]: ... 104 | @overload 105 | def convert_tree( 106 | beautiful_soup_tree: BeautifulSoup, 107 | makeelement: _ElementFactory[_ET], 108 | ) -> list[_ET]: ... 109 | @overload 110 | def convert_tree( 111 | beautiful_soup_tree: BeautifulSoup, 112 | makeelement: None = None, 113 | ) -> list[HtmlElement]: ... 114 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/isoschematron.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any, ClassVar, overload 3 | 4 | if sys.version_info >= (3, 11): 5 | from typing import LiteralString 6 | else: 7 | from typing_extensions import LiteralString 8 | 9 | from . import etree as _e 10 | from ._types import _ElementOrTree, _FileReadSource 11 | from .etree._xslt import _Stylesheet_Param 12 | 13 | __all__ = ( 14 | # Official exports 15 | "extract_xsd", 16 | "extract_rng", 17 | "iso_dsdl_include", 18 | "iso_abstract_expand", 19 | "iso_svrl_for_xslt1", 20 | "svrl_validation_errors", 21 | "schematron_schema_valid", 22 | "schematron_schema_valid_supported", 23 | "stylesheet_params", 24 | "Schematron", 25 | # Namespace constants 26 | "XML_SCHEMA_NS", 27 | "RELAXNG_NS", 28 | "SCHEMATRON_NS", 29 | "SVRL_NS", 30 | ) 31 | 32 | XML_SCHEMA_NS: LiteralString 33 | RELAXNG_NS: LiteralString 34 | SCHEMATRON_NS: LiteralString 35 | SVRL_NS: LiteralString 36 | 37 | extract_xsd: _e.XSLT 38 | extract_rng: _e.XSLT 39 | iso_dsdl_include: _e.XSLT 40 | iso_abstract_expand: _e.XSLT 41 | iso_svrl_for_xslt1: _e.XSLT 42 | svrl_validation_errors: _e.XPath 43 | schematron_schema_valid: _e.RelaxNG 44 | schematron_schema_valid_supported: bool 45 | 46 | def stylesheet_params(**__kw: str | _e.XPath | Any) -> dict[str, _Stylesheet_Param]: ... 47 | 48 | class Schematron(_e._Validator): 49 | _domain: ClassVar[_e.ErrorDomains] 50 | _level: ClassVar[_e.ErrorLevels] 51 | _error_type: ClassVar[_e.ErrorTypes] 52 | ASSERTS_ONLY: ClassVar[_e.XPath] 53 | ASSERTS_AND_REPORTS: ClassVar[_e.XPath] 54 | _extract_xsd: ClassVar[_e.XSLT] 55 | _extract_rng: ClassVar[_e.XSLT] 56 | _include: ClassVar[_e.XSLT] 57 | _expand: ClassVar[_e.XSLT] 58 | _compile: ClassVar[_e.XSLT] 59 | _validation_errors: ClassVar[_e.XPath] 60 | # _extract() can be a mean of customisation like some of the vars above 61 | def _extract(self, element: _e._Element) -> _e._ElementTree | None: ... 62 | 63 | # The overload arg matrix would have been daunting (3 * 2**3): 64 | # - etree / file 65 | # - include / include_params 66 | # - expand / expand_params 67 | # - compile_params / phase 68 | # Therefore we just distinguish etree and file arg, following 69 | # how other validators are done. 70 | # Besides, error_finder default value is too complex, and 71 | # validate_schema default is dependent on runtime system, 72 | # so ellipsis is preserved here instead of explicitly listing. 73 | @overload 74 | def __init__( 75 | self, 76 | etree: _ElementOrTree, 77 | file: None = None, 78 | include: bool = True, 79 | expand: bool = True, 80 | include_params: dict[str, _Stylesheet_Param] = {}, 81 | expand_params: dict[str, _Stylesheet_Param] = {}, 82 | compile_params: dict[str, _Stylesheet_Param] = {}, 83 | store_schematron: bool = False, 84 | store_xslt: bool = False, 85 | store_report: bool = False, 86 | phase: str | None = None, 87 | error_finder: _e.XPath = ..., # keep ellipsis 88 | validate_schema: bool = ..., # keep ellipsis 89 | ) -> None: ... 90 | @overload 91 | def __init__( 92 | self, 93 | etree: None, 94 | file: _FileReadSource, 95 | include: bool = True, 96 | expand: bool = True, 97 | include_params: dict[str, _Stylesheet_Param] = {}, 98 | expand_params: dict[str, _Stylesheet_Param] = {}, 99 | compile_params: dict[str, _Stylesheet_Param] = {}, 100 | store_schematron: bool = False, 101 | store_xslt: bool = False, 102 | store_report: bool = False, 103 | phase: str | None = None, 104 | error_finder: _e.XPath = ..., # keep ellipsis 105 | validate_schema: bool = ..., # keep ellipsis 106 | ) -> None: ... 107 | @overload 108 | def __init__( 109 | self, 110 | *, 111 | file: _FileReadSource, 112 | include: bool = True, 113 | expand: bool = True, 114 | include_params: dict[str, _Stylesheet_Param] = {}, 115 | expand_params: dict[str, _Stylesheet_Param] = {}, 116 | compile_params: dict[str, _Stylesheet_Param] = {}, 117 | store_schematron: bool = False, 118 | store_xslt: bool = False, 119 | store_report: bool = False, 120 | phase: str | None = None, 121 | error_finder: _e.XPath = ..., # keep ellipsis 122 | validate_schema: bool = ..., # keep ellipsis 123 | ) -> None: ... 124 | def __call__(self, etree: _ElementOrTree) -> bool: ... 125 | @property 126 | def schematron(self) -> _e._XSLTResultTree | None: ... 127 | @property 128 | def validator_xslt(self) -> _e._XSLTResultTree | None: ... 129 | @property 130 | def validation_report(self) -> _e._XSLTResultTree | None: ... 131 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/objectify/__init__.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 11): 4 | from typing import LiteralString 5 | else: 6 | from typing_extensions import LiteralString 7 | 8 | from ._annotate import ( 9 | PyType as PyType, 10 | annotate as annotate, 11 | deannotate as deannotate, 12 | getRegisteredTypes as getRegisteredTypes, 13 | pyannotate as pyannotate, 14 | pytypename as pytypename, 15 | set_pytype_attribute_tag as set_pytype_attribute_tag, 16 | xsiannotate as xsiannotate, 17 | ) 18 | from ._element import ( 19 | BoolElement as BoolElement, 20 | FloatElement as FloatElement, 21 | IntElement as IntElement, 22 | NoneElement as NoneElement, 23 | ObjectifiedDataElement as ObjectifiedDataElement, 24 | ObjectifiedElement as ObjectifiedElement, 25 | StringElement as StringElement, 26 | ) 27 | from ._factory import ( 28 | DataElement as DataElement, 29 | E as E, 30 | Element as Element, 31 | ElementMaker as ElementMaker, 32 | SubElement as SubElement, 33 | ) 34 | from ._misc import ( 35 | XML as XML, 36 | ObjectifyElementClassLookup as ObjectifyElementClassLookup, 37 | ObjectPath as ObjectPath, 38 | dump as dump, 39 | enable_recursive_str as enable_recursive_str, 40 | fromstring as fromstring, 41 | makeparser as makeparser, 42 | parse as parse, 43 | set_default_parser as set_default_parser, 44 | ) 45 | 46 | # Exported constants 47 | __version__: LiteralString 48 | PYTYPE_ATTRIBUTE: LiteralString 49 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/server/additional_stubs/lxml/lxml/py.typed -------------------------------------------------------------------------------- /server/additional_stubs/lxml/lxml/sax.pyi: -------------------------------------------------------------------------------- 1 | from typing import Generic, overload 2 | from xml.sax.handler import ContentHandler 3 | 4 | from ._types import _ET, SupportsLaxedItems, Unused, _ElementFactory, _ElementOrTree 5 | from .etree import LxmlError, _ElementTree, _ProcessingInstruction 6 | 7 | class SaxError(LxmlError): ... 8 | 9 | # xml.sax.handler is annotated in typeshed since Sept 2023. 10 | class ElementTreeContentHandler(Generic[_ET], ContentHandler): 11 | _root: _ET | None 12 | _root_siblings: list[_ProcessingInstruction] 13 | _element_stack: list[_ET] 14 | _default_ns: str | None 15 | _ns_mapping: dict[str | None, list[str | None]] 16 | _new_mappings: dict[str | None, str] 17 | # Not adding _get_etree(), already available as public property 18 | @overload 19 | def __new__( 20 | cls, makeelement: _ElementFactory[_ET] 21 | ) -> ElementTreeContentHandler[_ET]: ... 22 | @overload 23 | def __new__(cls, makeelement: None = None) -> ElementTreeContentHandler: ... 24 | @property 25 | def etree(self) -> _ElementTree[_ET]: ... 26 | 27 | # Incompatible method overrides; some args are similar 28 | # but use other structures or names 29 | def startElementNS( # pyright: ignore[reportIncompatibleMethodOverride] 30 | self, 31 | ns_name: tuple[str, str], 32 | qname: Unused, 33 | attributes: SupportsLaxedItems[tuple[str | None, str], str] | None = None, 34 | ) -> None: ... 35 | def endElementNS( # pyright: ignore[reportIncompatibleMethodOverride] 36 | self, 37 | ns_name: tuple[str, str], 38 | qname: Unused, 39 | ) -> None: ... 40 | def characters( # pyright: ignore[reportIncompatibleMethodOverride] 41 | self, 42 | data: str, 43 | ) -> None: ... 44 | def startElement( # pyright: ignore[reportIncompatibleMethodOverride] 45 | self, 46 | name: str, 47 | attributes: SupportsLaxedItems[str, str] | None = None, 48 | ) -> None: ... 49 | ignorableWhitespace = characters # type: ignore[assignment] 50 | 51 | class ElementTreeProducer(Generic[_ET]): 52 | _element: _ET 53 | _content_handler: ContentHandler 54 | # The purpose of _attr_class and _empty_attributes is 55 | # more like a shortcut. These attributes are constant and 56 | # doesn't help debugging 57 | def __init__( 58 | self, 59 | element_or_tree: _ElementOrTree[_ET], 60 | content_handler: ContentHandler, 61 | ) -> None: ... 62 | def saxify(self) -> None: ... 63 | 64 | # equivalent to saxify() in ElementTreeProducer 65 | def saxify( 66 | element_or_tree: _ElementOrTree, 67 | content_handler: ContentHandler, 68 | ) -> None: ... 69 | -------------------------------------------------------------------------------- /server/additional_stubs/lxml/pyproject.toml: -------------------------------------------------------------------------------- 1 | #:schema https://json.schemastore.org/pyproject.json 2 | 3 | [build-system] 4 | requires = ['pdm-backend ~= 2.3'] 5 | build-backend = 'pdm.backend' 6 | 7 | [project] 8 | name = 'types-lxml' 9 | dynamic = ['version'] 10 | description = 'Complete lxml external type annotation' 11 | readme = 'README.md' 12 | requires-python = '>=3.8' 13 | license = {text = 'Apache-2.0'} 14 | dependencies = [ 15 | 'types-beautifulsoup4 ~= 4.12', 16 | 'typing_extensions ~= 4.5', 17 | 'cssselect ~= 1.2' # cssselect uses inline annotation 18 | ] 19 | keywords = ['lxml', 'typing', 'stubs', 'annotation'] 20 | authors = [ 21 | { name = 'Abel Cheung', email = 'abelcheung@gmail.com' } 22 | ] 23 | classifiers = [ 24 | 'Development Status :: 5 - Production/Stable', 25 | 'Programming Language :: Python', 26 | 'Intended Audience :: Developers', 27 | 'Programming Language :: Python :: 3 :: Only', 28 | 'Programming Language :: Python :: 3.8', 29 | 'Programming Language :: Python :: 3.9', 30 | 'Programming Language :: Python :: 3.10', 31 | 'Programming Language :: Python :: 3.11', 32 | 'Programming Language :: Python :: 3.12', 33 | 'Typing :: Stubs Only', 34 | ] 35 | 36 | [project.urls] 37 | homepage = 'https://github.com/abelcheung/types-lxml' 38 | 39 | [project.optional-dependencies] 40 | test = [ 41 | 'tox ~= 4.0', 42 | 'mypy >= 1.9, < 1.11', 43 | 'pyright >= 1.1.289', 44 | 'typeguard >= 3.0, < 5', 45 | 'pytest >= 7.0, < 9', 46 | 'html5lib == 1.1', 47 | 'pytest-mypy-plugins >= 2.0', 48 | 'lxml >= 4.9', 49 | 'beautifulsoup4 ~= 4.8' 50 | ] 51 | 52 | [tool.pdm.version] 53 | source = 'scm' 54 | 55 | [tool.pdm.build] 56 | includes = [ 57 | 'lxml-stubs/', 58 | ] 59 | excludes = [ 60 | '**/*.bak', 61 | '*.patch', 62 | '*.py', 63 | '**/.mypy_cache', 64 | '**/.pytest_cache', 65 | ] 66 | source-includes = [ 67 | 'requirements.txt', 68 | 'test-rt/', 69 | 'test-stub/', 70 | 'tox.ini', 71 | ] 72 | 73 | [tool.setuptools_scm] 74 | 75 | [tool.isort] 76 | profile = 'black' 77 | combine_as_imports = true 78 | skip = [".git", ".github", ".venv", ".tox"] 79 | extra_standard_library = [ 80 | "typing_extensions", 81 | "_typeshed", 82 | ] 83 | 84 | [tool.pyright] 85 | include = ['lxml-stubs'] 86 | reportPrivateUsage = false 87 | reportUnnecessaryIsInstance = false 88 | typeCheckingMode = 'strict' 89 | 90 | [tool.mypy] 91 | packages = 'lxml-stubs' 92 | strict = true 93 | show_error_context = true 94 | pretty = true 95 | 96 | [tool.ruff] 97 | target-version = "py312" 98 | 99 | [tool.ruff.lint] 100 | select = ["I"] 101 | task-tags = [ 102 | "BUG", 103 | "FIXME", 104 | "HACK", 105 | "TODO", 106 | "XXX", 107 | ] 108 | 109 | [tool.ruff.lint.isort] 110 | # doesn't work 111 | # known-first-party = ["typing_extensions", "_typeshed"] 112 | combine-as-imports = true 113 | -------------------------------------------------------------------------------- /server/benches/iai_profiler.rs: -------------------------------------------------------------------------------- 1 | use lsp_server::Notification; 2 | use serde_json::json; 3 | use odoo_ls_server::{args::{Cli, LogLevel}, cli_backend::CliBackend, constants::*, server::Server, utils::PathSanitizer}; 4 | use clap::Parser; 5 | use tracing::{info, Level}; 6 | use tracing_appender::rolling::{RollingFileAppender, Rotation}; 7 | use tracing_panic::panic_hook; 8 | use tracing_subscriber::{fmt, FmtSubscriber, layer::SubscriberExt}; 9 | 10 | use std::{env, path::PathBuf}; 11 | 12 | use iai_callgrind::{ 13 | library_benchmark, library_benchmark_group, main, LibraryBenchmarkConfig, 14 | FlamegraphConfig 15 | }; 16 | use std::hint::black_box; 17 | 18 | /* 19 | To run iai-callgrind: 20 | install valgrind 21 | run cargo install --version 0.14.0 iai-callgrind-runner 22 | */ 23 | 24 | #[library_benchmark] 25 | fn iai_main() { 26 | env::set_var("RUST_BACKTRACE", "full"); 27 | 28 | let log_level = LogLevel::INFO; 29 | let log_level = match log_level { 30 | LogLevel::TRACE => Level::TRACE, 31 | LogLevel::DEBUG => Level::DEBUG, 32 | LogLevel::INFO => Level::INFO, 33 | LogLevel::WARN => Level::WARN, 34 | LogLevel::ERROR => Level::ERROR, 35 | }; 36 | 37 | let mut exe_dir = env::current_exe().expect("Unable to get binary directory... aborting"); 38 | exe_dir.pop(); 39 | 40 | let mut log_dir = exe_dir.join("logs").sanitize(); 41 | 42 | let file_appender = RollingFileAppender::builder() 43 | .max_log_files(5) // only the most recent 5 log files will be kept 44 | .rotation(Rotation::HOURLY) 45 | .filename_prefix("odoo_logs") 46 | .filename_suffix(format!("{}.log", std::process::id())) 47 | .build(log_dir) 48 | .expect("failed to initialize rolling file appender"); 49 | let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); 50 | let subscriber = FmtSubscriber::builder() 51 | .with_thread_ids(true) 52 | .with_file(false) 53 | .with_max_level(log_level) 54 | .with_ansi(false) 55 | .with_writer(file_writer) 56 | .finish(); 57 | let stdout_subscriber = fmt::layer().with_writer(std::io::stdout).with_ansi(true); 58 | tracing::subscriber::set_global_default(subscriber.with(stdout_subscriber)).expect("Unable to set default tracing subscriber"); 59 | ctrlc::set_handler(move || { 60 | info!("Received ctrl-c signal"); 61 | std::process::exit(0); 62 | }) 63 | .expect("Error setting Ctrl-C handler"); 64 | 65 | info!(">>>>>>>>>>>>>>>>>> New Session <<<<<<<<<<<<<<<<<<"); 66 | info!("Server version: {}", EXTENSION_VERSION); 67 | info!("Compiled setting: DEBUG_ODOO_BUILDER: {}", DEBUG_ODOO_BUILDER); 68 | info!("Compiled setting: DEBUG_MEMORY: {}", DEBUG_MEMORY); 69 | info!("Operating system: {}", std::env::consts::OS); 70 | info!(""); 71 | info!("starting server"); 72 | info!(tag = "test", "starting server (debug mode)"); 73 | let mut serv = Server::new_tcp().expect("Unable to start tcp connection"); 74 | serv.initialize().expect("Error while initializing server"); 75 | let sender_panic = serv.connection.as_ref().unwrap().sender.clone(); 76 | std::panic::set_hook(Box::new(move |panic_info| { 77 | panic_hook(panic_info); 78 | let _ = sender_panic.send(lsp_server::Message::Notification(Notification{ 79 | method: "Odoo/displayCrashNotification".to_string(), 80 | params: json!({ 81 | "crashInfo": format!("{panic_info}"), 82 | "pid": std::process::id() 83 | }) 84 | })); 85 | })); 86 | black_box(serv.run(None)); 87 | info!(">>>>>>>>>>>>>>>>>> End Session <<<<<<<<<<<<<<<<<<"); 88 | } 89 | 90 | library_benchmark_group!(name = my_group; benchmarks = iai_main); 91 | 92 | main!( 93 | config = LibraryBenchmarkConfig::default().flamegraph(FlamegraphConfig::default()); 94 | library_benchmark_groups = my_group 95 | ); -------------------------------------------------------------------------------- /server/src/allocator.rs: -------------------------------------------------------------------------------- 1 | use std::alloc::{GlobalAlloc, Layout, System}; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | pub struct TrackingAllocator; 5 | 6 | pub static ALLOCATED: AtomicUsize = AtomicUsize::new(0); 7 | 8 | unsafe impl GlobalAlloc for TrackingAllocator { 9 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 10 | let size = layout.size(); 11 | ALLOCATED.fetch_add(size, Ordering::SeqCst); 12 | System.alloc(layout) 13 | } 14 | 15 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 16 | let size = layout.size(); 17 | ALLOCATED.fetch_sub(size, Ordering::SeqCst); 18 | System.dealloc(ptr, layout) 19 | } 20 | 21 | unsafe fn realloc( 22 | &self, 23 | ptr: *mut u8, 24 | old_layout: Layout, 25 | new_size: usize, 26 | ) -> *mut u8 { 27 | let old_size = old_layout.size(); 28 | ALLOCATED.fetch_sub(old_size, Ordering::SeqCst); 29 | ALLOCATED.fetch_add(new_size, Ordering::SeqCst); 30 | System.realloc(ptr, old_layout, new_size) 31 | } 32 | } -------------------------------------------------------------------------------- /server/src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, ValueEnum}; 2 | 3 | #[derive(Parser)] 4 | #[command(version, about, long_about = None)] 5 | #[allow(non_snake_case)] 6 | pub struct Cli { 7 | //Do not run the server, but only extract diagnostics from the codebase, then stop. 8 | #[arg(short, long)] 9 | pub parse: bool, 10 | 11 | //addon paths you want to parse (parse mode required) 12 | #[arg(short, long)] 13 | pub addons: Option>, 14 | 15 | //community path (parse mode required) 16 | #[arg(short, long)] 17 | pub community_path: Option, 18 | 19 | //Tracked folders. Diagnostics will only be raised if they are in a file inside one of these directory 20 | //by default populated with all odoo directories + addon paths (parse mode required) 21 | #[arg(short, long)] 22 | pub tracked_folders: Option>, 23 | 24 | //python path to use (parse mode required) 25 | #[arg(long)] 26 | pub python: Option, 27 | 28 | //output path. Default to "output.json" 29 | #[arg(short, long)] 30 | pub output: Option, 31 | 32 | #[arg(short, long)] 33 | //additional stubs directories. Be careful that each stub must be in a directory with its own name. 34 | pub stubs: Option>, 35 | 36 | //Remove Typeshed stubs. Useful if you want to provide your own version of stubs. It does not remove stdlib stubs however (they are required), only stubs of external packages 37 | #[arg(long)] 38 | pub no_typeshed: bool, 39 | 40 | //give an alternative path to stdlib stubs. 41 | #[arg(long)] 42 | pub stdlib: Option, 43 | 44 | //Provide a pid (unix only) that the server will listen and kill itself if the process stop. 45 | #[arg(long)] 46 | pub clientProcessId: Option, 47 | 48 | #[arg(long)] 49 | pub use_tcp: bool, 50 | 51 | #[arg(value_enum, long, default_value="trace")] 52 | pub log_level: LogLevel, 53 | 54 | //provide a path to the directory that will be used for logs 55 | #[arg(long)] 56 | pub logs_directory: Option, 57 | } 58 | 59 | #[derive(ValueEnum, Clone, Debug)] 60 | pub enum LogLevel { 61 | TRACE, 62 | DEBUG, 63 | INFO, 64 | WARN, 65 | ERROR, 66 | } -------------------------------------------------------------------------------- /server/src/cli_backend.rs: -------------------------------------------------------------------------------- 1 | use lsp_server::Message; 2 | use lsp_types::notification::{LogMessage, Notification, PublishDiagnostics}; 3 | use lsp_types::{LogMessageParams, PublishDiagnosticsParams}; 4 | use tracing::{error, info}; 5 | 6 | use crate::threads::SessionInfo; 7 | use crate::utils::PathSanitizer; 8 | use crate::args::Cli; 9 | use std::io::Write; 10 | use std::path::PathBuf; 11 | use std::fs::File; 12 | use serde_json::json; 13 | use crate::core::{config::{Config, DiagMissingImportsMode}, odoo::SyncOdoo}; 14 | use crate::S; 15 | 16 | 17 | /// Basic backend that is used for a single parse execution 18 | pub struct CliBackend { 19 | cli: Cli 20 | } 21 | 22 | impl CliBackend { 23 | 24 | pub fn new(cli: Cli) -> Self { 25 | CliBackend { 26 | cli 27 | } 28 | } 29 | 30 | pub fn run(&self) { 31 | let community_path = self.cli.community_path.clone(); 32 | let mut server = SyncOdoo::new(); 33 | let (s, r) = crossbeam_channel::unbounded(); 34 | let mut session = SessionInfo::new_from_custom_channel(s.clone(), r.clone(), &mut server); 35 | session.sync_odoo.load_odoo_addons = false; 36 | 37 | let addons_paths = self.cli.addons.clone().unwrap_or(vec![]); 38 | info!("Using addons path: {:?}", addons_paths); 39 | 40 | let workspace_folders = self.cli.tracked_folders.clone().unwrap_or(vec![]); 41 | info!("Using tracked folders: {:?}", workspace_folders); 42 | 43 | for tracked_folder in workspace_folders { 44 | session.sync_odoo.get_file_mgr().borrow_mut().add_workspace_folder(PathBuf::from(tracked_folder).sanitize()); 45 | } 46 | 47 | let mut config = Config::new(); 48 | config.addons = addons_paths; 49 | config.odoo_path = community_path; 50 | config.python_path = S!("python3"); 51 | config.refresh_mode = crate::core::config::RefreshMode::Off; 52 | config.diag_missing_imports = DiagMissingImportsMode::All; 53 | config.no_typeshed = self.cli.no_typeshed; 54 | config.additional_stubs = self.cli.stubs.clone().unwrap_or(vec![]); 55 | config.stdlib = self.cli.stdlib.clone().unwrap_or(S!("")); 56 | SyncOdoo::init(&mut session, config); 57 | 58 | let output_path = self.cli.output.clone().unwrap_or(S!("output.json")); 59 | let file = File::create(output_path.clone()); 60 | let mut events = vec![]; 61 | if let Ok(mut file) = file { 62 | while !r.is_empty() { 63 | let msg = r.recv(); 64 | if let Ok(msg) = msg { 65 | match msg { 66 | Message::Notification(n) => { 67 | match n.method.as_str() { 68 | LogMessage::METHOD => { 69 | let params: LogMessageParams = serde_json::from_value(n.params).unwrap(); 70 | events.push(json!({ 71 | "type": "log", 72 | "severity": params.typ, 73 | "message": params.message 74 | })) 75 | }, 76 | PublishDiagnostics::METHOD => { 77 | let mut diagnostics = vec![]; 78 | let params: PublishDiagnosticsParams = serde_json::from_value(n.params).unwrap(); 79 | for diagnostic in params.diagnostics.iter() { 80 | diagnostics.push(serde_json::to_value(diagnostic).unwrap()); 81 | } 82 | events.push(json!({ 83 | "type": "diagnostic", 84 | "uri": params.uri, 85 | "version": params.version, 86 | "diagnostics": diagnostics 87 | })); 88 | }, 89 | _ => {error!("not handled method: {}", n.method)} 90 | } 91 | }, 92 | Message::Request(_) => { 93 | error!("No request should be sent to client as we are in cli mode."); 94 | }, 95 | Message::Response(_) => { 96 | error!("No response should be sent to client as we are in cli mode."); 97 | } 98 | } 99 | } else { 100 | error!("Unable to recv a message"); 101 | } 102 | } 103 | let json_string = json!({"events": events}); 104 | if let Err(e) = file.write_all(serde_json::to_string_pretty(&json_string).unwrap().as_bytes()) { 105 | error!("Unable to write to {}: {}", output_path, e) 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /server/src/constants.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | use core::fmt; 3 | 4 | use byteyarn::{yarn, Yarn}; 5 | 6 | pub const EXTENSION_NAME: &str = "Odoo"; 7 | pub const EXTENSION_VERSION: &str = "0.6.3"; 8 | 9 | pub const DEBUG_ODOO_BUILDER: bool = false; 10 | pub const DEBUG_MEMORY: bool = false; 11 | pub const DEBUG_THREADS: bool = false; 12 | pub const DEBUG_STEPS: bool = false; 13 | pub const DEBUG_REBUILD_NOW: bool = false; 14 | 15 | pub type Tree = (Vec, Vec); 16 | 17 | //type DebugYarn = String; 18 | 19 | #[macro_export] 20 | #[cfg(not(all(feature="debug_yarn", debug_assertions)))] 21 | macro_rules! oyarn { 22 | ($($args:tt)*) => { 23 | byteyarn::yarn!($($args)*) 24 | }; 25 | } 26 | #[macro_export] 27 | #[cfg(all(feature="debug_yarn", debug_assertions))] 28 | macro_rules! oyarn { 29 | ($($args:tt)*) => { 30 | format!($($args)*) 31 | }; 32 | } 33 | 34 | #[cfg(all(feature="debug_yarn", debug_assertions))] 35 | pub type OYarn = String; 36 | #[cfg(not(all(feature="debug_yarn", debug_assertions)))] 37 | pub type OYarn = Yarn; 38 | 39 | pub fn tree(a: Vec<&str>, b: Vec<&str>) -> Tree { 40 | (a.iter().map(|x| oyarn!("{}", *x)).collect(), b.iter().map(|x| oyarn!("{}", *x)).collect()) 41 | } 42 | 43 | pub fn flatten_tree(tree: &Tree) -> Vec { 44 | [tree.0.clone(), tree.1.clone()].concat() 45 | } 46 | 47 | 48 | #[derive(Debug, Eq, Hash, PartialEq, Copy, Clone)] 49 | pub enum PackageType{ 50 | PYTHON_PACKAGE, 51 | MODULE 52 | } 53 | 54 | #[derive(Debug, Eq, Hash, PartialEq, Copy, Clone)] 55 | pub enum SymType{ 56 | ROOT, 57 | DISK_DIR, 58 | NAMESPACE, 59 | PACKAGE(PackageType), 60 | FILE, 61 | COMPILED, 62 | VARIABLE, 63 | CLASS, 64 | FUNCTION 65 | } 66 | 67 | impl fmt::Display for SymType { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | write!(f, "{:?}", self) 70 | } 71 | } 72 | 73 | #[derive(Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Copy, Clone)] 74 | pub enum BuildSteps { 75 | SYNTAX = -1, //can't be 0, because others should be able to be used as vec index 76 | ARCH = 0, 77 | ARCH_EVAL = 1, 78 | VALIDATION = 2, 79 | } 80 | 81 | impl From for BuildSteps { 82 | fn from(value: i32) -> Self { 83 | match value { 84 | -1 => BuildSteps::SYNTAX, 85 | 0 => BuildSteps::ARCH, 86 | 1 => BuildSteps::ARCH_EVAL, 87 | 2 => BuildSteps::VALIDATION, 88 | _ => panic!("Invalid value for BuildSteps: {}", value), 89 | } 90 | } 91 | } 92 | 93 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 94 | pub enum BuildStatus { 95 | PENDING, 96 | IN_PROGRESS, 97 | INVALID, 98 | DONE 99 | } 100 | 101 | pub const BUILT_IN_LIBS: &[&str] = &["string", "re", "difflib", "textwrap", "unicodedata", "stringprep", "readline", "rlcompleter", 102 | "datetime", "zoneinfo", "calendar", "collections", "heapq", "bisect", "array", "weakref", "types", "copy", "pprint", 103 | "reprlib", "enum", "graphlib", "numbers", "math", "cmath", "decimal", "fractions", "random", "statistics", "itertools", 104 | "functools", "operator", "pathlib", "fileinput", "stat", "filecmp", "tempfile", "glob", "fnmatch", "linecache", 105 | "shutil", "pickle", "copyreg", "shelve", "marshal", "dbm", "sqlite3", "zlib", "gzip", "bz2", "lzma", "zipfile", 106 | "tarfile", "csv", "configparser", "tomllib", "netrc", "plistlib", "hashlib", "hmac", "secrets", "os", "io", "time", 107 | "argparse", "getopt", "logging", "getpass", "curses", "platform", "errno", "ctypes", "threading", "multiprocessing", 108 | "concurrent", "subprocess", "sched", "queue", "contextvars", "_thread", "asyncio", "socket", "ssl", "select", 109 | "selectors", "signal", "mmap", "email", "json", "mailbox", "mimetypes", "base64", "binascii", "quopri", "html", 110 | "xml", "webbrowser", "wsgiref", "urllib", "http", "ftplib", "poplib", "imaplib", "smtplib", "uuid", "socketserver", 111 | "xmlrpc", "ipaddress", "wave", "colorsys", "gettext", "locale", "turtle", "cmd", "shlex", "tkinter", "IDLE", 112 | "typing", "pydoc", "doctest", "unittest", "2to3", "test", "bdb", "faulthandler", "pdb", "timeit", "trace", 113 | "tracemalloc", "distutils", "ensurepip", "venv", "zipapp", "sys", "sysconfig", "builtins", "__main__", "warnings", 114 | "dataclasses", "contextlib", "abc", "atexit", "traceback", "__future__", "gc", "inspect", "site", "code", "codeop", 115 | "zipimport", "pkgutil", "modulefinder", "runpy", "importlib", "ast", "symtable", "token", "keyword", "tokenize", 116 | "tabnanny", "pyclbr", "py_compile", "compileall", "dis", "pickletools", "msvcrt", "winreg", "winsound", "posix", 117 | "pwd", "grp", "termios", "tty", "pty", "fcntl", "resource", "syslog", "aifc", "asynchat", "asyncore", "audioop", 118 | "cgi", "cgitb", "chunk", "crypt", "imghdr", "imp", "mailcap", "msilib", "nis", "nntplib", "optparse", "ossaudiodev", 119 | "pipes", "smtpd", "sndhdr", "spwd", "sunau", "telnetlib", "uu", "xdrlib", "struct", "codecs"]; -------------------------------------------------------------------------------- /server/src/core/config.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use lsp_types::request::Request; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub enum RefreshMode { 7 | OnSave, 8 | Adaptive, 9 | Off 10 | } 11 | 12 | impl FromStr for RefreshMode { 13 | 14 | type Err = (); 15 | 16 | fn from_str(input: &str) -> Result { 17 | match input { 18 | "afterDelay" => Ok(RefreshMode::Adaptive), 19 | "onSave" => Ok(RefreshMode::OnSave), 20 | "adaptive" => Ok(RefreshMode::Adaptive), 21 | "off" => Ok(RefreshMode::Off), 22 | _ => Err(()), 23 | } 24 | } 25 | } 26 | 27 | #[derive(Debug, PartialEq, Clone)] 28 | pub enum DiagMissingImportsMode { 29 | None, 30 | OnlyOdoo, 31 | All 32 | } 33 | 34 | impl FromStr for DiagMissingImportsMode { 35 | 36 | type Err = (); 37 | 38 | fn from_str(input: &str) -> Result { 39 | match input { 40 | "none" => Ok(DiagMissingImportsMode::None), 41 | "only_odoo" => Ok(DiagMissingImportsMode::OnlyOdoo), 42 | "all" => Ok(DiagMissingImportsMode::All), 43 | _ => Err(()), 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone)] 49 | pub struct Config { 50 | pub refresh_mode: RefreshMode, 51 | pub auto_save_delay: u64, 52 | pub file_cache: bool, 53 | pub diag_missing_imports: DiagMissingImportsMode, 54 | pub diag_only_opened_files: bool, 55 | pub addons: Vec, 56 | pub odoo_path: Option, 57 | pub python_path: String, 58 | pub no_typeshed: bool, 59 | pub additional_stubs: Vec, 60 | pub stdlib: String, 61 | pub ac_filter_model_names: bool, // AC: Only show model names from module dependencies 62 | } 63 | 64 | impl Config { 65 | pub fn new() -> Self { 66 | Self { 67 | refresh_mode: RefreshMode::Adaptive, 68 | auto_save_delay: 1000, 69 | file_cache: true, 70 | diag_missing_imports: DiagMissingImportsMode::All, 71 | diag_only_opened_files: false, 72 | addons: Vec::new(), 73 | odoo_path: None, 74 | python_path: "python3".to_string(), 75 | no_typeshed: false, 76 | additional_stubs: vec![], 77 | stdlib: "".to_string(), 78 | ac_filter_model_names: false, 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /server/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod evaluation; 3 | pub mod entry_point; 4 | pub mod file_mgr; 5 | pub mod import_resolver; 6 | pub mod model; 7 | pub mod odoo; 8 | pub mod python_arch_builder; 9 | pub mod python_arch_builder_hooks; 10 | pub mod python_arch_eval; 11 | pub mod python_arch_eval_hooks; 12 | pub mod python_odoo_builder; 13 | pub mod python_validator; 14 | pub mod python_utils; 15 | pub mod symbols; -------------------------------------------------------------------------------- /server/src/core/python_arch_builder_hooks.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::rc::Rc; 3 | use std::cell::RefCell; 4 | use tracing::warn; 5 | use crate::core::symbols::symbol::Symbol; 6 | use crate::threads::SessionInfo; 7 | use crate::{Sy, S}; 8 | use crate::constants::OYarn; 9 | 10 | use super::odoo::SyncOdoo; 11 | 12 | pub struct PythonArchBuilderHooks {} 13 | 14 | impl PythonArchBuilderHooks { 15 | 16 | pub fn on_class_def(session: &mut SessionInfo, symbol: Rc>) { 17 | let mut sym = symbol.borrow_mut(); 18 | let name = &sym.name(); 19 | match name.as_str() { 20 | "BaseModel" => { 21 | if sym.get_main_entry_tree(session) == (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("BaseModel")]) { 22 | // ----------- env ------------ 23 | let env = sym.get_symbol(&(vec![], vec![Sy!("env")]), u32::MAX); 24 | if env.is_empty() { 25 | let mut range = sym.range().clone(); 26 | let slots = sym.get_symbol(&(vec![], vec![Sy!("__slots__")]), u32::MAX); 27 | if slots.len() == 1 { 28 | if slots.len() == 1 { 29 | range = slots[0].borrow().range().clone(); 30 | } 31 | } 32 | sym.add_new_variable(session, Sy!("env"), &range); 33 | } 34 | } 35 | }, 36 | "Environment" => { 37 | if sym.get_main_entry_tree(session) == (vec![Sy!("odoo"), Sy!("api")], vec![Sy!("Environment")]) { 38 | let new_sym = sym.get_symbol(&(vec![], vec![Sy!("__new__")]), u32::MAX); 39 | let mut range = sym.range().clone(); 40 | if new_sym.len() == 1 { 41 | range = new_sym[0].borrow().range().clone(); 42 | } 43 | // ----------- env.cr ------------ 44 | sym.add_new_variable(session, Sy!("cr"), &range); 45 | // ----------- env.uid ------------ 46 | let uid_sym = sym.add_new_variable(session, Sy!("uid"), &range); 47 | uid_sym.borrow_mut().as_variable_mut().doc_string = Some(S!("The current user id (for access rights checks)")); 48 | // ----------- env.context ------------ 49 | let context_sym = sym.add_new_variable(session, Sy!("context"), &range); 50 | context_sym.borrow_mut().as_variable_mut().doc_string = Some(S!("The current context")); 51 | // ----------- env.su ------------ 52 | let su_sym = sym.add_new_variable(session, Sy!("su"), &range); 53 | su_sym.borrow_mut().as_variable_mut().doc_string = Some(S!("whether in superuser mode")); 54 | // ----------- env.registry ----------- 55 | let _ = sym.add_new_variable(session, Sy!("registry"), &range); 56 | } 57 | }, 58 | "Boolean" | "Integer" | "Float" | "Monetary" | "Char" | "Text" | "Html" | "Date" | "Datetime" | 59 | "Binary" | "Image" | "Selection" | "Reference" | "Many2one" | "Many2oneReference" | "Json" | 60 | "Properties" | "PropertiesDefinition" | "One2many" | "Many2many" | "Id" => { 61 | if sym.get_main_entry_tree(session).0 == vec![Sy!("odoo"), Sy!("fields")] { 62 | // ----------- __get__ ------------ 63 | let get_sym = sym.get_symbol(&(vec![], vec![Sy!("__get__")]), u32::MAX); 64 | if get_sym.is_empty() { 65 | let range = sym.range().clone(); 66 | sym.add_new_function(session, &S!("__get__"), &range, &range.end()); 67 | } else { 68 | if !["Id", "One2many"].contains(&name.as_str()){ 69 | warn!("Found __get__ function for field of name ({})", name); 70 | } 71 | } 72 | // ----------- __init__ ------------ 73 | let get_sym = sym.get_symbol(&(vec![], vec![Sy!("__init__")]), u32::MAX); 74 | if get_sym.is_empty() { 75 | let range = sym.range().clone(); 76 | sym.add_new_function(session, &S!("__init__"), &range, &range.end()); 77 | } 78 | } 79 | } 80 | _ => {} 81 | } 82 | } 83 | 84 | pub fn on_done(session: &mut SessionInfo, symbol: &Rc>) { 85 | let name = symbol.borrow().name().clone(); 86 | if name == "release" { 87 | if symbol.borrow().get_main_entry_tree(session) == (vec![Sy!("odoo"), Sy!("release")], vec![]) { 88 | let (maj, min, mic) = SyncOdoo::read_version(session, PathBuf::from(symbol.borrow().paths()[0].clone())); 89 | if maj != session.sync_odoo.version_major || min != session.sync_odoo.version_minor || mic != session.sync_odoo.version_micro { 90 | session.sync_odoo.need_rebuild = true; 91 | } 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /server/src/core/symbols/class_symbol.rs: -------------------------------------------------------------------------------- 1 | use byteyarn::Yarn; 2 | use ruff_text_size::{TextRange, TextSize}; 3 | use std::collections::HashMap; 4 | use std::rc::{Rc, Weak}; 5 | use std::cell::RefCell; 6 | use weak_table::PtrWeakHashSet; 7 | 8 | use crate::constants::{OYarn, SymType}; 9 | use crate::core::file_mgr::NoqaInfo; 10 | use crate::core::model::ModelData; 11 | use crate::threads::SessionInfo; 12 | use crate::{Sy, S}; 13 | 14 | use super::symbol::Symbol; 15 | use super::symbol_mgr::{SectionRange, SymbolMgr}; 16 | 17 | 18 | #[derive(Debug)] 19 | pub struct ClassSymbol { 20 | pub name: OYarn, 21 | pub is_external: bool, 22 | pub doc_string: Option, 23 | pub bases: Vec>>, 24 | pub ast_indexes: Vec, //list of index to reach the corresponding ast node from file ast 25 | pub weak_self: Option>>, 26 | pub parent: Option>>, 27 | pub range: TextRange, 28 | pub body_range: TextRange, 29 | pub _model: Option, 30 | pub noqas: NoqaInfo, 31 | 32 | //Trait SymbolMgr 33 | //--- Body symbols 34 | pub sections: Vec, 35 | pub symbols: HashMap>>>>, 36 | //--- dynamics variables 37 | pub ext_symbols: HashMap>>>, 38 | } 39 | 40 | impl ClassSymbol { 41 | 42 | pub fn new(name: String, range: TextRange, body_start: TextSize, is_external: bool) -> Self { 43 | let mut res = Self { 44 | name: OYarn::from(name), 45 | is_external, 46 | weak_self: None, 47 | parent: None, 48 | range, 49 | body_range: TextRange::new(body_start, range.end()), 50 | ast_indexes: vec![], 51 | doc_string: None, 52 | sections: vec![], 53 | symbols: HashMap::new(), 54 | ext_symbols: HashMap::new(), 55 | bases: vec![], 56 | _model: None, 57 | noqas: NoqaInfo::None, 58 | }; 59 | res._init_symbol_mgr(); 60 | res 61 | } 62 | 63 | pub fn inherits(&self, base: &Rc>, checked: &mut Option>>>) -> bool { 64 | if checked.is_none() { 65 | *checked = Some(PtrWeakHashSet::new()); 66 | } 67 | for base_weak in self.bases.iter() { 68 | let b = match base_weak.upgrade(){ 69 | Some(b) => b, 70 | None => continue 71 | }; 72 | if Rc::ptr_eq(&b, base) { 73 | return true; 74 | } 75 | if checked.as_ref().unwrap().contains(&b) { 76 | return false; 77 | } 78 | checked.as_mut().unwrap().insert(b.clone()); 79 | if b.borrow().as_class_sym().inherits(base, checked) { 80 | return true; 81 | } 82 | } 83 | false 84 | } 85 | 86 | pub fn add_symbol(&mut self, content: &Rc>, section: u32) { 87 | let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::new()); 88 | let section_vec = sections.entry(section).or_insert(vec![]); 89 | section_vec.push(content.clone()); 90 | } 91 | 92 | pub fn is_descriptor(&self) -> bool { 93 | for get_sym in self.get_content_symbol(Sy!("__get__"), u32::MAX).symbols.iter() { 94 | if get_sym.borrow().typ() == SymType::FUNCTION { 95 | return true; 96 | } 97 | } 98 | false 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /server/src/core/symbols/compiled_symbol.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; 2 | 3 | use crate::constants::OYarn; 4 | 5 | use super::symbol::Symbol; 6 | 7 | #[derive(Debug)] 8 | pub struct CompiledSymbol { 9 | pub name: OYarn, 10 | pub is_external: bool, 11 | pub path: String, 12 | pub weak_self: Option>>, 13 | pub parent: Option>>, 14 | pub module_symbols: HashMap>>, 15 | } 16 | 17 | impl CompiledSymbol { 18 | 19 | pub fn new(name: String, path: String, is_external: bool) -> Self { 20 | Self { 21 | name: OYarn::from(name), 22 | is_external, 23 | weak_self:None, 24 | path, 25 | module_symbols: HashMap::new(), 26 | parent: None, 27 | } 28 | } 29 | 30 | pub fn add_compiled(&mut self, compiled: &Rc>) { 31 | self.module_symbols.insert(compiled.borrow().name().clone(), compiled.clone()); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /server/src/core/symbols/disk_dir_symbol.rs: -------------------------------------------------------------------------------- 1 | use weak_table::PtrWeakHashSet; 2 | 3 | use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::{Rc, Weak}}; 4 | 5 | use crate::{constants::OYarn, oyarn, threads::SessionInfo, utils::PathSanitizer}; 6 | 7 | use super::symbol::Symbol; 8 | 9 | /* 10 | DiskDir symbol represent a directory on disk we didn't parse yet. So it can either be a namespace or a package later. 11 | */ 12 | #[derive(Debug)] 13 | pub struct DiskDirSymbol { 14 | pub name: OYarn, 15 | pub path: String, 16 | pub module_symbols: HashMap>>, 17 | pub is_external: bool, 18 | pub weak_self: Option>>, 19 | pub parent: Option>>, 20 | pub in_workspace: bool, 21 | } 22 | 23 | impl DiskDirSymbol { 24 | 25 | pub fn new(name: String, path: String, is_external: bool) -> Self { 26 | Self { 27 | name: oyarn!("{}", name), 28 | path: PathBuf::from(path).sanitize(), 29 | is_external, 30 | weak_self: None, 31 | parent: None, 32 | in_workspace: false, 33 | module_symbols: HashMap::new() 34 | } 35 | } 36 | 37 | pub fn add_file(&mut self, file: &Rc>) { 38 | self.module_symbols.insert(file.borrow().name().clone(), file.clone()); 39 | } 40 | 41 | /*pub fn load(sesion: &mut SessionInfo, dir: &Rc>) -> Rc> { 42 | let path = dir.borrow().as_disk_dir_sym().path.clone(); 43 | }*/ 44 | } -------------------------------------------------------------------------------- /server/src/core/symbols/file_symbol.rs: -------------------------------------------------------------------------------- 1 | use weak_table::PtrWeakHashSet; 2 | 3 | use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model}, oyarn}; 4 | use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; 5 | 6 | use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; 7 | 8 | #[derive(Debug)] 9 | pub struct FileSymbol { 10 | pub name: OYarn, 11 | pub path: String, 12 | pub is_external: bool, 13 | pub weak_self: Option>>, 14 | pub parent: Option>>, 15 | pub arch_status: BuildStatus, 16 | pub arch_eval_status: BuildStatus, 17 | pub odoo_status: BuildStatus, 18 | pub validation_status: BuildStatus, 19 | pub not_found_paths: Vec<(BuildSteps, Vec)>, 20 | in_workspace: bool, 21 | pub self_import: bool, 22 | pub model_dependencies: PtrWeakHashSet>>, //always on validation level, as odoo step is always required 23 | pub dependencies: Vec>>>>>, 24 | pub dependents: Vec>>>>>, 25 | pub processed_text_hash: u64, 26 | pub noqas: NoqaInfo, 27 | 28 | //Trait SymbolMgr 29 | pub sections: Vec, 30 | pub symbols: HashMap>>>>, 31 | //--- dynamics variables 32 | pub ext_symbols: HashMap>>>, 33 | } 34 | 35 | impl FileSymbol { 36 | 37 | pub fn new(name: String, path: String, is_external: bool) -> Self { 38 | let mut res = Self { 39 | name: oyarn!("{}", name), 40 | path, 41 | is_external, 42 | weak_self: None, 43 | parent: None, 44 | arch_status: BuildStatus::PENDING, 45 | arch_eval_status: BuildStatus::PENDING, 46 | odoo_status: BuildStatus::PENDING, 47 | validation_status: BuildStatus::PENDING, 48 | not_found_paths: vec![], 49 | in_workspace: false, 50 | self_import: false, 51 | sections: vec![], 52 | symbols: HashMap::new(), 53 | ext_symbols: HashMap::new(), 54 | model_dependencies: PtrWeakHashSet::new(), 55 | dependencies: vec![], 56 | dependents: vec![], 57 | processed_text_hash: 0, 58 | noqas: NoqaInfo::None, 59 | }; 60 | res._init_symbol_mgr(); 61 | res 62 | } 63 | 64 | pub fn add_symbol(&mut self, content: &Rc>, section: u32) { 65 | let sections = self.symbols.entry(content.borrow().name().clone()).or_insert_with(|| HashMap::new()); 66 | let section_vec = sections.entry(section).or_insert_with(|| vec![]); 67 | section_vec.push(content.clone()); 68 | } 69 | 70 | pub fn get_dependencies(&self, step: usize, level: usize) -> Option<&PtrWeakHashSet>>> 71 | { 72 | self.dependencies.get(step)?.get(level)?.as_ref() 73 | } 74 | 75 | pub fn get_all_dependencies(&self, step: usize) -> Option<&Vec>>>>> 76 | { 77 | self.dependencies.get(step) 78 | } 79 | 80 | pub fn get_dependents(&self, level: usize, step: usize) -> Option<&PtrWeakHashSet>>> 81 | { 82 | self.dependents.get(level)?.get(step)?.as_ref() 83 | } 84 | 85 | pub fn get_all_dependents(&self, level: usize) -> Option<&Vec>>>>> 86 | { 87 | self.dependents.get(level) 88 | } 89 | 90 | pub fn dependencies(&self) -> &Vec>>>>> { 91 | &self.dependencies 92 | } 93 | 94 | pub fn dependencies_mut(&mut self) -> &mut Vec>>>>> { 95 | &mut self.dependencies 96 | } 97 | 98 | pub fn set_in_workspace(&mut self, in_workspace: bool) { 99 | self.in_workspace = in_workspace; 100 | if in_workspace { 101 | self.dependencies= vec![ 102 | vec![ //ARCH 103 | None //ARCH 104 | ], 105 | vec![ //ARCH_EVAL 106 | None, //ARCH, 107 | None, //ARCH_EVAL 108 | ], 109 | vec![ 110 | None, // ARCH 111 | None, //ARCH_EVAL 112 | None, //VALIDATIOn 113 | ] 114 | ]; 115 | self.dependents = vec![ 116 | vec![ //ARCH 117 | None, //ARCH 118 | None, //ARCH_EVAL 119 | None, //VALIDATION 120 | ], 121 | vec![ //ARCH_EVAL 122 | None, //ARCH_EVAL 123 | None //VALIDATION 124 | ], 125 | vec![ //VALIDATION 126 | None //VALIDATION 127 | ] 128 | ]; 129 | } 130 | } 131 | 132 | pub fn dependents(&self) -> &Vec>>>>> { 133 | &self.dependents 134 | } 135 | 136 | pub fn dependents_mut(&mut self) -> &mut Vec>>>>> { 137 | &mut self.dependents 138 | } 139 | 140 | pub fn is_in_workspace(&self) -> bool { 141 | self.in_workspace 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /server/src/core/symbols/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod class_symbol; 2 | pub mod compiled_symbol; 3 | pub mod disk_dir_symbol; 4 | pub mod file_symbol; 5 | pub mod function_symbol; 6 | pub mod module_symbol; 7 | pub mod namespace_symbol; 8 | pub mod package_symbol; 9 | pub mod root_symbol; 10 | pub mod symbol; 11 | pub mod symbol_mgr; 12 | pub mod variable_symbol; -------------------------------------------------------------------------------- /server/src/core/symbols/namespace_symbol.rs: -------------------------------------------------------------------------------- 1 | use byteyarn::Yarn; 2 | use weak_table::PtrWeakHashSet; 3 | 4 | use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::{Rc, Weak}}; 5 | 6 | use crate::constants::{BuildSteps, OYarn}; 7 | 8 | use super::symbol::Symbol; 9 | 10 | 11 | #[derive(Debug)] 12 | pub struct NamespaceDirectory { 13 | pub path: String, 14 | pub module_symbols: HashMap>>, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct NamespaceSymbol { 19 | pub name: OYarn, 20 | pub directories: Vec, 21 | pub is_external: bool, 22 | pub weak_self: Option>>, 23 | pub parent: Option>>, 24 | in_workspace: bool, 25 | pub dependencies: Vec>>>>>, 26 | pub dependents: Vec>>>>>, 27 | } 28 | 29 | impl NamespaceSymbol { 30 | 31 | pub fn new(name: String, paths: Vec, is_external: bool) -> Self { 32 | let mut directories = vec![]; 33 | for p in paths.iter() { 34 | directories.push(NamespaceDirectory { 35 | path: p.clone(), 36 | module_symbols: HashMap::new(), 37 | }) 38 | } 39 | Self { 40 | name: OYarn::from(name), 41 | directories: directories, 42 | is_external, 43 | weak_self: None, 44 | parent: None, 45 | in_workspace: false, 46 | dependencies: vec![], 47 | dependents: vec![], 48 | } 49 | } 50 | 51 | pub fn add_file(&mut self, file: &Rc>) { 52 | let mut best_index: i32 = -1; 53 | let mut best_length: i32 = -1; 54 | let mut index = 0; 55 | while index < self.directories.len() { 56 | if PathBuf::from(&file.borrow().paths()[0]).starts_with(&self.directories[index].path) && self.directories[index].path.len() as i32 > best_length { 57 | best_index = index as i32; 58 | best_length = self.directories[index].path.len() as i32; 59 | } 60 | index += 1; 61 | } 62 | if best_index == -1 { 63 | panic!("Not valid path found to add the file ({}) to namespace {} with directories {:?}", file.borrow().paths()[0], self.name, self.directories); 64 | } else { 65 | self.directories[best_index as usize].module_symbols.insert(file.borrow().name().clone(), file.clone()); 66 | } 67 | } 68 | 69 | pub fn paths(&self) -> Vec { 70 | self.directories.iter().map(|x| {x.path.clone()}).collect() 71 | } 72 | 73 | pub fn get_dependencies(&self, step: usize, level: usize) -> Option<&PtrWeakHashSet>>> 74 | { 75 | self.dependencies.get(step)?.get(level)?.as_ref() 76 | } 77 | 78 | pub fn get_all_dependencies(&self, step: usize) -> Option<&Vec>>>>> 79 | { 80 | self.dependencies.get(step) 81 | } 82 | 83 | pub fn dependencies(&self) -> &Vec>>>>> { 84 | &self.dependencies 85 | } 86 | 87 | pub fn dependencies_mut(&mut self) -> &mut Vec>>>>> { 88 | &mut self.dependencies 89 | } 90 | 91 | pub fn dependents(&self) -> &Vec>>>>> { 92 | &self.dependents 93 | } 94 | 95 | pub fn dependents_mut(&mut self) -> &mut Vec>>>>> { 96 | &mut self.dependents 97 | } 98 | 99 | pub fn get_dependents(&self, level: usize, step: usize) -> Option<&PtrWeakHashSet>>> 100 | { 101 | self.dependents.get(level)?.get(step)?.as_ref() 102 | } 103 | 104 | pub fn get_all_dependents(&self, level: usize) -> Option<&Vec>>>>> 105 | { 106 | self.dependents.get(level) 107 | } 108 | 109 | pub fn set_in_workspace(&mut self, in_workspace: bool) { 110 | self.in_workspace = in_workspace; 111 | if in_workspace { 112 | self.dependencies= vec![ 113 | vec![ //ARCH 114 | None //ARCH 115 | ], 116 | vec![ //ARCH_EVAL 117 | None, //ARCH, 118 | None, //ARCH_EVAL 119 | ], 120 | vec![ 121 | None, // ARCH 122 | None, //ARCH_EVAL 123 | None, //VALIDATIOn 124 | ] 125 | ]; 126 | self.dependents = vec![ 127 | vec![ //ARCH 128 | None, //ARCH 129 | None, //ARCH_EVAL 130 | None, //VALIDATION 131 | ], 132 | vec![ //ARCH_EVAL 133 | None, //ARCH_EVAL 134 | None //VALIDATION 135 | ], 136 | vec![ //VALIDATION 137 | None //VALIDATION 138 | ] 139 | ]; 140 | } 141 | } 142 | 143 | pub fn is_in_workspace(&self) -> bool { 144 | self.in_workspace 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /server/src/core/symbols/root_symbol.rs: -------------------------------------------------------------------------------- 1 | use crate::{constants::{BuildSteps, OYarn}, core::entry_point::EntryPoint, oyarn, threads::SessionInfo, S}; 2 | use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; 3 | 4 | use super::symbol::Symbol; 5 | 6 | #[derive(Debug)] 7 | pub struct RootSymbol { 8 | pub name: OYarn, 9 | pub entry_point: Option>>, 10 | pub paths: Vec, 11 | pub weak_self: Option>>, 12 | pub parent: Option>>, 13 | pub module_symbols: HashMap>>, 14 | } 15 | 16 | impl RootSymbol { 17 | 18 | pub fn new() -> Self { 19 | Self { 20 | name: oyarn!("Root"), 21 | paths: vec![], 22 | weak_self: None, 23 | entry_point: None, 24 | parent: None, 25 | module_symbols: HashMap::new(), 26 | } 27 | } 28 | 29 | pub fn add_file(&mut self, file: &Rc>) { 30 | file.borrow_mut().set_is_external(true); 31 | self.module_symbols.insert(file.borrow().name().clone(), file.clone()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /server/src/core/symbols/variable_symbol.rs: -------------------------------------------------------------------------------- 1 | use ruff_text_size::TextRange; 2 | 3 | use crate::{constants::{OYarn, SymType}, core::evaluation::Evaluation, oyarn, threads::SessionInfo}; 4 | use std::{cell::RefCell, rc::{Rc, Weak}}; 5 | 6 | use super::symbol::Symbol; 7 | 8 | #[derive(Debug)] 9 | pub struct VariableSymbol { 10 | pub name: OYarn, 11 | pub is_external: bool, 12 | pub doc_string: Option, 13 | pub ast_indexes: Vec, //list of index to reach the corresponding ast node from file ast 14 | pub weak_self: Option>>, 15 | pub parent: Option>>, 16 | pub is_import_variable: bool, 17 | pub is_parameter: bool, 18 | pub evaluations: Vec, //Vec, because sometimes a single allocation can be ambiguous, like ''' a = "5" if X else 5 ''' 19 | pub range: TextRange, 20 | } 21 | 22 | impl VariableSymbol { 23 | 24 | pub fn new(name: OYarn, range: TextRange, is_external: bool) -> Self { 25 | Self { 26 | name, 27 | is_external, 28 | doc_string: None, 29 | ast_indexes: vec![], 30 | weak_self: None, 31 | parent: None, 32 | range, 33 | is_import_variable: false, 34 | is_parameter: false, 35 | evaluations: vec![], 36 | } 37 | } 38 | 39 | pub fn is_type_alias(&self) -> bool { 40 | //TODO it does not use get_symbol call, and only evaluate "sym" from EvaluationSymbol 41 | return self.evaluations.len() >= 1 && self.evaluations.iter().all(|x| !x.symbol.is_instance().unwrap_or(true)) && !self.is_import_variable; 42 | } 43 | 44 | // pub fn full_size_of(self) -> serde_json::Value { 45 | // let name_to_add = if self.name.len() > 15 { 46 | // self.name.len() 47 | // } else { 48 | // 0 49 | // }; 50 | // let mut evals = 0; 51 | // for eval in self.evaluations.iter() { 52 | // evals += eval.full_size_of(); 53 | // } 54 | // size_of::() + 55 | // name_to_add + 56 | // self.doc_string.map(|x| x.capacity()).unwrap_or(0) + 57 | // self.ast_indexes.capacity() + 58 | // evals 59 | // } 60 | 61 | /* If this variable has been evaluated to a relational field, return the main symbol of the comodel */ 62 | pub fn get_relational_model(&self, session: &mut SessionInfo, from_module: Option>>) -> Vec>> { 63 | for eval in self.evaluations.iter() { 64 | let symbol = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); 65 | let eval_weaks = Symbol::follow_ref(&symbol, session, &mut None, false, false, None, &mut vec![]); 66 | for eval_weak in eval_weaks.iter() { 67 | if let Some(symbol) = eval_weak.upgrade_weak() { 68 | if ["Many2one", "One2many", "Many2many"].contains(&symbol.borrow().name().as_str()) { 69 | let Some(comodel) = eval_weak.as_weak().context.get("comodel_name") else { 70 | continue; 71 | }; 72 | let Some(model) = session.sync_odoo.models.get(&oyarn!("{}", &comodel.as_string())).cloned() else { 73 | continue; 74 | }; 75 | return model.borrow().get_main_symbols(session, from_module); 76 | } else if symbol.borrow().typ() == SymType::CLASS { // Already evaluated from descriptor in follow_ref 77 | return vec![symbol]; 78 | } 79 | } 80 | } 81 | } 82 | vec![] 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /server/src/features/hover.rs: -------------------------------------------------------------------------------- 1 | use lsp_types::{Hover, HoverContents, MarkupContent, Range}; 2 | use crate::core::file_mgr::FileInfo; 3 | use crate::threads::SessionInfo; 4 | use std::rc::Rc; 5 | use crate::core::symbols::symbol::Symbol; 6 | use crate::features::ast_utils::AstUtils; 7 | use crate::features::features_utils::FeaturesUtils; 8 | use std::cell::RefCell; 9 | 10 | 11 | pub struct HoverFeature {} 12 | 13 | impl HoverFeature { 14 | 15 | pub fn get_hover(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option { 16 | let offset = file_info.borrow().position_to_offset(line, character); 17 | let (analyse_ast_result, range, call_expr) = AstUtils::get_symbols(session, file_symbol, file_info, offset as u32); 18 | let evals = analyse_ast_result.evaluations; 19 | if evals.is_empty() { 20 | return None; 21 | }; 22 | let range = Some(Range { 23 | start: file_info.borrow().offset_to_position(range.unwrap().start().to_usize()), 24 | end: file_info.borrow().offset_to_position(range.unwrap().end().to_usize()) 25 | }); 26 | Some(Hover { contents: 27 | HoverContents::Markup(MarkupContent { 28 | kind: lsp_types::MarkupKind::Markdown, 29 | value: FeaturesUtils::build_markdown_description(session, Some(file_symbol.clone()), &evals, &call_expr, Some(offset)) 30 | }), 31 | range: range 32 | }) 33 | } 34 | } -------------------------------------------------------------------------------- /server/src/features/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod completion; 2 | pub mod definition; 3 | pub mod document_symbols; 4 | pub mod hover; 5 | pub mod ast_utils; 6 | pub mod features_utils; -------------------------------------------------------------------------------- /server/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod allocator; 2 | pub mod args; 3 | pub mod cli_backend; 4 | pub mod constants; 5 | pub mod core; 6 | pub mod threads; 7 | pub mod features; 8 | pub mod server; 9 | pub mod tasks; 10 | pub mod utils; -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | use lsp_server::Notification; 2 | use serde_json::json; 3 | use odoo_ls_server::{args::{Cli, LogLevel}, cli_backend::CliBackend, constants::*, server::Server, utils::PathSanitizer}; 4 | use clap::Parser; 5 | use tracing::{info, Level}; 6 | use tracing_appender::rolling::{RollingFileAppender, Rotation}; 7 | use tracing_panic::panic_hook; 8 | use tracing_subscriber::{fmt, FmtSubscriber, layer::SubscriberExt}; 9 | 10 | use std::{env, path::PathBuf, process}; 11 | use odoo_ls_server::allocator::TrackingAllocator; 12 | 13 | #[cfg(debug_assertions)] 14 | #[global_allocator] 15 | static GLOBAL: TrackingAllocator = TrackingAllocator; 16 | 17 | fn main() { 18 | env::set_var("RUST_BACKTRACE", "full"); 19 | let cli = Cli::parse(); 20 | 21 | let use_debug = cli.use_tcp; 22 | let log_level = &cli.log_level; 23 | let log_level = match log_level { 24 | LogLevel::TRACE => Level::TRACE, 25 | LogLevel::DEBUG => Level::DEBUG, 26 | LogLevel::INFO => Level::INFO, 27 | LogLevel::WARN => Level::WARN, 28 | LogLevel::ERROR => Level::ERROR, 29 | }; 30 | 31 | let mut exe_dir = env::current_exe().expect("Unable to get binary directory... aborting"); 32 | exe_dir.pop(); 33 | 34 | let mut log_dir = exe_dir.join("logs").sanitize(); 35 | if let Some(log_directory) = cli.logs_directory.clone() { 36 | let pathbuf = PathBuf::from(log_directory); 37 | if pathbuf.exists() { 38 | log_dir = pathbuf.sanitize(); 39 | } else { 40 | println!("Given log directory path is invalid, fallbacking to default directory {}", log_dir); 41 | } 42 | } 43 | 44 | let file_appender = RollingFileAppender::builder() 45 | .max_log_files(5) // only the most recent 5 log files will be kept 46 | .rotation(Rotation::HOURLY) 47 | .filename_prefix("odoo_logs") 48 | .filename_suffix(format!("{}.log", std::process::id())) 49 | .build(log_dir) 50 | .expect("failed to initialize rolling file appender"); 51 | let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); 52 | let subscriber = FmtSubscriber::builder() 53 | .with_thread_ids(true) 54 | .with_file(false) 55 | .with_max_level(log_level) 56 | .with_ansi(false) 57 | .with_writer(file_writer) 58 | .finish(); 59 | if cli.parse || use_debug { 60 | let stdout_subscriber = fmt::layer().with_writer(std::io::stdout).with_ansi(true); 61 | tracing::subscriber::set_global_default(subscriber.with(stdout_subscriber)).expect("Unable to set default tracing subscriber"); 62 | } else { 63 | tracing::subscriber::set_global_default(subscriber).expect("Unable to set default tracing subscriber"); 64 | } 65 | ctrlc::set_handler(move || { 66 | info!("Received ctrl-c signal"); 67 | std::process::exit(0); 68 | }) 69 | .expect("Error setting Ctrl-C handler"); 70 | 71 | info!(">>>>>>>>>>>>>>>>>> New Session <<<<<<<<<<<<<<<<<<"); 72 | info!("Server version: {}", EXTENSION_VERSION); 73 | info!("Compiled setting: DEBUG_ODOO_BUILDER: {}", DEBUG_ODOO_BUILDER); 74 | info!("Compiled setting: DEBUG_MEMORY: {}", DEBUG_MEMORY); 75 | info!("Compiled setting: DEBUG_THREADS: {}", DEBUG_THREADS); 76 | info!("Compiled setting: DEBUG_STEPS: {}", DEBUG_STEPS); 77 | info!("Compiled setting: DEBUG_REBUILD_NOW: {}", DEBUG_REBUILD_NOW); 78 | info!("Operating system: {}", std::env::consts::OS); 79 | info!(""); 80 | 81 | if cli.parse { 82 | info!("starting server (single parse mode)"); 83 | let backend = CliBackend::new(cli); 84 | backend.run(); 85 | } else if use_debug { 86 | info!(tag = "test", "starting server (debug mode)"); 87 | let mut serv = Server::new_tcp().expect("Unable to start tcp connection"); 88 | serv.initialize().expect("Error while initializing server"); 89 | let sender_panic = serv.connection.as_ref().unwrap().sender.clone(); 90 | std::panic::set_hook(Box::new(move |panic_info| { 91 | let backtrace = std::backtrace::Backtrace::capture(); 92 | panic_hook(panic_info); 93 | let _ = sender_panic.send(lsp_server::Message::Notification(Notification{ 94 | method: "Odoo/displayCrashNotification".to_string(), 95 | params: json!({ 96 | "crashInfo": format!("{panic_info}\n\nTraceback:\n{backtrace}"), 97 | "pid": std::process::id() 98 | }) 99 | })); 100 | })); 101 | if !serv.run(cli.clientProcessId) { 102 | info!(">>>>>>>>>>>>>>>>>> End Session <<<<<<<<<<<<<<<<<<"); 103 | process::exit(1); 104 | } 105 | } else { 106 | info!("starting server"); 107 | let mut serv = Server::new_stdio(); 108 | serv.initialize().expect("Error while initializing server"); 109 | let sender_panic = serv.connection.as_ref().unwrap().sender.clone(); 110 | std::panic::set_hook(Box::new(move |panic_info| { 111 | panic_hook(panic_info); 112 | let backtrace = std::backtrace::Backtrace::capture(); 113 | let _ = sender_panic.send(lsp_server::Message::Notification(Notification{ 114 | method: "Odoo/displayCrashNotification".to_string(), 115 | params: json!({ 116 | "crashInfo": format!("{panic_info}\n\nTraceback:\n{backtrace}"), 117 | "pid": std::process::id() 118 | }) 119 | })); 120 | })); 121 | if !serv.run(cli.clientProcessId) { 122 | info!(">>>>>>>>>>>>>>>>>> End Session <<<<<<<<<<<<<<<<<<"); 123 | process::exit(1); 124 | } 125 | } 126 | info!(">>>>>>>>>>>>>>>>>> End Session <<<<<<<<<<<<<<<<<<"); 127 | } 128 | -------------------------------------------------------------------------------- /server/src/tasks.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/server/src/tasks.rs -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import data -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Part of Odoo. See LICENSE file for full copyright and licensing details. 3 | { 4 | 'name' : 'Module 1', 5 | 'version' : '1.0', 6 | 'summary': 'Test Module 1', 7 | 'sequence': 10, 8 | 'description': """ 9 | Module 1 10 | ==================== 11 | This is the description of the module 1 12 | """, 13 | 'category': 'Accounting/Accounting', 14 | 'depends' : [], 15 | 'installable': True, 16 | 'application': True, 17 | 'license': 'LGPL-3', 18 | } -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/constants/__init__.py: -------------------------------------------------------------------------------- 1 | from .data import * -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/constants/data/__init__.py: -------------------------------------------------------------------------------- 1 | from .constants import * 2 | 3 | CONSTANT_2 = 22 -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/constants/data/constants.py: -------------------------------------------------------------------------------- 1 | __all__ = ["CONSTANT_1", "CONSTANT_2"] 2 | 3 | CONSTANT_1 = 1 4 | CONSTANT_2 = 2 5 | CONSTANT_3 = 3 -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import base_test_models 2 | from . import models 3 | from . import to_complete -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/models/base_test_models.py: -------------------------------------------------------------------------------- 1 | from odoo import api, fields, models, _, tools 2 | from odoo.addons.module_1.constants import CONSTANT_1, CONSTANT_2 3 | 4 | class BaseTestModel(models.Model): 5 | _name = "pygls.tests.base_test_model" 6 | _inherit = [] 7 | _description = "Base Test Model" 8 | 9 | test_int = fields.Integer() 10 | 11 | def get_test_int(self): 12 | self.ensure_one() 13 | return self.test_int 14 | 15 | def get_constant(self): 16 | return CONSTANT_1 + CONSTANT_2 17 | 18 | def for_func(self): 19 | for var in self: 20 | print(var) 21 | 22 | BaseOtherName = BaseTestModel 23 | baseInstance1 = BaseTestModel() 24 | baseInstance2 = BaseOtherName() 25 | ref_funcBase1 = BaseTestModel.get_test_int 26 | ref_funcBase2 = baseInstance1.get_test_int 27 | return_funcBase2 = baseInstance2.get_test_int() -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/models/models.py: -------------------------------------------------------------------------------- 1 | from odoo import models, fields 2 | 3 | class model_model(models.Model): 4 | pass 5 | 6 | class model_transient(models.TransientModel): 7 | pass 8 | 9 | class model_abstract(models.AbstractModel): 10 | pass 11 | 12 | class model_name(models.Model): 13 | _name = "pygls.tests.m_name" 14 | _auto = False 15 | 16 | f1 = fields.Char() 17 | 18 | def func_1(self): 19 | pass 20 | 21 | class model_name_inh_python(model_name): 22 | pass 23 | 24 | class model_name_inherit(models.Model): 25 | _name = "pygls.tests.m_name" 26 | _inherit = "pygls.tests.m_name" 27 | 28 | class model_name_inherit_no_name(models.Model): 29 | _inherit = "pygls.tests.m_name" 30 | 31 | class model_name_inherit_diff_name(models.Model): 32 | _name = "pygls.tests.m_diff_name" 33 | _inherit = "pygls.tests.m_name" 34 | 35 | class model_name_2(models.Model): 36 | _name = "pygls.tests.m_name_2" 37 | 38 | class model_name_inherit_comb_name(models.Model): 39 | _name = "pygls.tests.m_comb_name" 40 | _inherit = ["pygls.tests.m_name", "pygls.tests.m_name_2"] 41 | 42 | an_int = fields.Integer() 43 | a_bool = fields.Boolean() 44 | a_char = fields.Char() 45 | a_text = fields.Text() 46 | a_float = fields.Float() 47 | a_date = fields.Date() 48 | a_datetime = fields.Datetime() 49 | a_selection = fields.Selection() 50 | 51 | def a_random_func(self): 52 | self.an_int 53 | self.a_bool 54 | self.a_char 55 | self.a_text 56 | self.a_float 57 | self.a_date 58 | self.a_datetime 59 | self.a_selection 60 | 61 | class model_no_name(models.Model): 62 | pass 63 | 64 | class model_no_register(models.Model): 65 | _name = "pygls.tests.m_no_register" 66 | _register = False 67 | 68 | class model_register(model_no_register): 69 | _name = "pygls.tests.m_no_register" 70 | 71 | class model_no_register_inherit(models.Model): 72 | _name = "pygls.tests.m_no_register" 73 | _inherit = "pygls.tests.m_no_register" 74 | 75 | class model_inherits(models.Model): 76 | _name = "pygls.tests.m_inherits" 77 | _inherits = {"pygls.tests.m_name": "field_m_name_id"} 78 | 79 | field_m_name_id = fields.Many2one("pygls.tests.m_name") 80 | 81 | def a_random_func(self): 82 | self.field_m_name_id -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/models/to_complete.py: -------------------------------------------------------------------------------- 1 | from odoo import api, fields, models, _, tools 2 | 3 | 4 | class TestModel(models.Model): 5 | 6 | pass 7 | 8 | ExtraTestModel = TestModel 9 | SuperExtraTestModel = ExtraTestModel 10 | testModel = TestModel() 11 | extraTestModel = ExtraTestModel() 12 | superExtraTestModel = SuperExtraTestModel() -------------------------------------------------------------------------------- /server/tests/data/addons/module_1/not_loaded/not_loaded_file.py: -------------------------------------------------------------------------------- 1 | class NotLoadedClass(): 2 | pass 3 | 4 | def notLoadedFunc(): 5 | pass -------------------------------------------------------------------------------- /server/tests/data/addons/module_2/__init__.py: -------------------------------------------------------------------------------- 1 | from . import base_test_models -------------------------------------------------------------------------------- /server/tests/data/addons/module_2/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Part of Odoo. See LICENSE file for full copyright and licensing details. 3 | { 4 | 'name' : 'Module 2', 5 | 'version' : '1.0', 6 | 'summary': 'Test Module 2', 7 | 'sequence': 10, 8 | 'description': """ 9 | Module 2 10 | ==================== 11 | This is the description of the module 2 12 | """, 13 | 'category': 'Accounting/Accounting', 14 | 'depends' : ["module_1"], 15 | 'installable': True, 16 | 'application': True, 17 | 'license': 'LGPL-3', 18 | } -------------------------------------------------------------------------------- /server/tests/data/addons/module_2/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/server/tests/data/addons/module_2/models/__init__.py -------------------------------------------------------------------------------- /server/tests/data/addons/not_a_module/__init__.py: -------------------------------------------------------------------------------- 1 | import os -------------------------------------------------------------------------------- /server/tests/data/python/expressions/assign.py: -------------------------------------------------------------------------------- 1 | a = 5 2 | b = "test" 3 | c = 3.14 4 | d = True 5 | e = False 6 | f = None 7 | g = [1, 2, 3] 8 | h = (1, 2, 3) 9 | i = {"a": 1, "b": 2} 10 | j = {"a", "b", "c"} -------------------------------------------------------------------------------- /server/tests/data/python/expressions/sections.py: -------------------------------------------------------------------------------- 1 | 2 | if True: 3 | a = 5 4 | else: 5 | a = 6 6 | 7 | if True: 8 | b = 5 9 | else: 10 | b = 6 11 | b = 7 12 | 13 | c = 4 14 | if True: 15 | c = 5 16 | else: 17 | c = 6 18 | 19 | d = 4 20 | if True: 21 | d = 5 22 | 23 | e = 1 24 | if True: 25 | e = 2 26 | elif True: 27 | e = 3 28 | 29 | 30 | f = 33 31 | for y in range(3): 32 | if True: 33 | f = 32 34 | elif False: 35 | f = 34 36 | else: 37 | f = 35 38 | 39 | g = 99 40 | for _ in range(3): 41 | g = 98 42 | 43 | 44 | h = 99 45 | for _ in range(3): 46 | h = 98 47 | else: 48 | h = 5 49 | 50 | i = 77 51 | while i: 52 | i = 67 53 | else: 54 | i = 76 55 | 56 | j = 27 57 | while j: 58 | j = 37 59 | 60 | # 1. Only except, both try and except vals are possible 61 | try: 62 | k = 2 63 | except: 64 | k = 3 65 | k 66 | 67 | # 2. only values from catch-all except and else are possible 68 | try: 69 | l = 20 70 | except: 71 | l = 30 72 | else: 73 | l = 40 74 | l 75 | 76 | # 3. `finally` always runs 77 | m = 50 78 | try: 79 | m = 60 80 | except: 81 | m = 70 82 | finally: 83 | m = 80 84 | m 85 | 86 | # 4. `finally` always runs even with else 87 | o = 90 88 | try: 89 | o = 100 90 | except: 91 | o = 110 92 | else: 93 | o = 140 94 | finally: 95 | o = 120 96 | o 97 | 98 | # 5. With specific exception handler, all 3 values are possible 99 | try: 100 | p = 20 101 | except ValueError: 102 | p = 30 103 | else: 104 | p = 40 105 | p 106 | 107 | q = 33 108 | match p: 109 | case 20: 110 | q = 34 111 | case 30: 112 | q = 43 113 | q 114 | 115 | r = 33 116 | match p: 117 | case 20: 118 | r = 34 119 | case x: 120 | r = 43 121 | r 122 | 123 | 124 | (_ := [1, 2, 33])[(s := 2): (t := 3)][0] 125 | 126 | u = 90 127 | if (u := 92): 128 | u = 91 129 | 130 | v = 70 131 | if (v := 71): 132 | v = 72 133 | elif (v := 73): 134 | v = 74 135 | v 136 | 137 | 138 | w = 70 139 | if (w := 71): 140 | w = 72 141 | elif w: 142 | w = 74 143 | w -------------------------------------------------------------------------------- /server/tests/setup/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod setup; 2 | pub mod setup_constants; -------------------------------------------------------------------------------- /server/tests/setup/setup.rs: -------------------------------------------------------------------------------- 1 | use core::str; 2 | use std::process::Command; 3 | use std::{env, fs}; 4 | 5 | use std::path::PathBuf; 6 | 7 | 8 | use lsp_types::TextDocumentContentChangeEvent; 9 | use odoo_ls_server::{core::{config::{Config, DiagMissingImportsMode}, entry_point::EntryPointMgr, odoo::SyncOdoo}, threads::SessionInfo, utils::PathSanitizer as _}; 10 | 11 | use odoo_ls_server::S; 12 | use tracing::{info, level_filters::LevelFilter}; 13 | use tracing_appender::rolling::RollingFileAppender; 14 | use tracing_subscriber::{fmt, layer::SubscriberExt, FmtSubscriber}; 15 | 16 | fn get_python_command() -> Option { 17 | for cmd in &["python3", "python"] { 18 | if let Ok(output) = Command::new(cmd).arg("--version").output() { 19 | if output.status.success() { 20 | return Some(S!(*cmd)); 21 | } 22 | } 23 | } 24 | None 25 | } 26 | 27 | pub fn setup_server(with_odoo: bool) -> SyncOdoo { 28 | 29 | let file_appender = RollingFileAppender::builder() 30 | .max_log_files(20) // only the most recent 5 log files will be kept 31 | .filename_prefix(format!("odoo_tests_logs_{}", std::process::id())) 32 | .filename_suffix("log") 33 | .build("./logs") 34 | .expect("failed to initialize rolling file appender"); 35 | let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); 36 | let subscriber = FmtSubscriber::builder() 37 | .with_thread_ids(true) 38 | .with_file(false) 39 | .with_max_level(LevelFilter::INFO) 40 | .with_ansi(false) 41 | .with_writer(file_writer) 42 | .finish(); 43 | let stdout_subscriber = fmt::layer().with_writer(std::io::stdout).with_ansi(true); 44 | let _ = tracing::subscriber::set_global_default(subscriber.with(stdout_subscriber)); 45 | 46 | 47 | let community_path = if with_odoo { 48 | Some(env::var("COMMUNITY_PATH").expect("Please provide COMMUNITY_PATH environment variable with a valid path to your Odoo Community folder")) 49 | } else { 50 | None 51 | }; 52 | let mut server = SyncOdoo::new(); 53 | server.load_odoo_addons = false; 54 | 55 | let mut test_addons_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 56 | test_addons_path = test_addons_path.join("tests").join("data").join("addons"); 57 | info!("Test addons path: {:?}", test_addons_path); 58 | 59 | let mut config = Config::new(); 60 | config.addons = vec![test_addons_path.sanitize()]; 61 | config.odoo_path = community_path.map(|x| PathBuf::from(x).sanitize()); 62 | let Some(python_cmd) = get_python_command() else { 63 | panic!("Python not found") 64 | }; 65 | config.python_path = python_cmd; 66 | config.refresh_mode = odoo_ls_server::core::config::RefreshMode::Off; 67 | config.diag_missing_imports = DiagMissingImportsMode::All; 68 | config.no_typeshed = false; 69 | 70 | let (s, r) = crossbeam_channel::unbounded(); 71 | let mut session = SessionInfo::new_from_custom_channel(s, r, &mut server); 72 | SyncOdoo::init(&mut session, config); 73 | 74 | server 75 | } 76 | 77 | pub fn create_session(odoo: &mut SyncOdoo) -> SessionInfo { 78 | let (s, r) = crossbeam_channel::unbounded(); 79 | SessionInfo::new_from_custom_channel(s.clone(), r.clone(), odoo) 80 | } 81 | 82 | pub fn prepare_custom_entry_point<'a>(odoo: &'a mut SyncOdoo, path: &str) -> SessionInfo<'a>{ 83 | let mut session = create_session(odoo); 84 | let ep_path = PathBuf::from(path).sanitize(); 85 | let text = fs::read_to_string(path).expect("unable to read provided path"); 86 | let content = Some(vec![TextDocumentContentChangeEvent{ 87 | range: None, 88 | range_length: None, 89 | text: text}]); 90 | let (file_updated, file_info) = session.sync_odoo.get_file_mgr().borrow_mut().update_file_info(&mut session, path, content.as_ref(), Some(1), false); 91 | EntryPointMgr::create_new_custom_entry_for_path(&mut session, &ep_path); 92 | SyncOdoo::process_rebuilds(&mut session); 93 | session 94 | } -------------------------------------------------------------------------------- /server/tests/setup/setup_constants.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/server/tests/setup/setup_constants.rs -------------------------------------------------------------------------------- /server/tests/test_setup.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use odoo_ls_server::constants::OYarn; 5 | use odoo_ls_server::utils::PathSanitizer; 6 | use odoo_ls_server::Sy; 7 | 8 | mod setup; 9 | 10 | /* This file tests that your setup is working, and show you how to write tests. */ 11 | 12 | #[test] 13 | fn test_setup() { 14 | assert!(env::var("COMMUNITY_PATH").is_ok(), "Please provide COMMUNITY_PATH environment variable with a valid path to your Odoo Community folder"); 15 | assert!(Path::new(&env::var("COMMUNITY_PATH").unwrap()).exists()); 16 | assert!(Path::new(&env::var("COMMUNITY_PATH").unwrap()).join("odoo").exists()); 17 | assert!(Path::new(&env::var("COMMUNITY_PATH").unwrap()).join("odoo").join("release.py").exists()); 18 | } 19 | 20 | #[test] 21 | fn test_start_odoo_server() { 22 | /* First, let's launch the server. It will setup a SyncOdoo struct, with a SyncChannel, that we can use to get the messages that the client would receive. */ 23 | let odoo = setup::setup::setup_server(true); 24 | 25 | let odoo_path = PathBuf::from(env::var("COMMUNITY_PATH").unwrap()).sanitize(); 26 | 27 | /* Let's ensure that the architecture is loaded */ 28 | assert!(!odoo.get_symbol(odoo_path.as_str(), &(vec![Sy!("odoo")], vec![]), u32::MAX).is_empty()); 29 | /* Let's ensure that odoo/addons is loaded */ 30 | assert!(!odoo.get_symbol(odoo_path.as_str(), &(vec![Sy!("odoo"), Sy!("addons")], vec![]), u32::MAX).is_empty()); 31 | /* And let's test that our test module has well been added and available in odoo/addons */ 32 | assert!(!odoo.get_symbol(odoo_path.as_str(), &(vec![Sy!("odoo"), Sy!("addons"), Sy!("module_1")], vec![]), u32::MAX).is_empty()); 33 | } -------------------------------------------------------------------------------- /vscode/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es2021: true 3 | node: true 4 | extends: 5 | - 'eslint:recommended' 6 | - 'plugin:@typescript-eslint/recommended' 7 | parser: '@typescript-eslint/parser' 8 | parserOptions: 9 | ecmaVersion: 12 10 | sourceType: module 11 | plugins: 12 | - '@typescript-eslint' 13 | rules: {} 14 | -------------------------------------------------------------------------------- /vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "1.0.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch VsCode Client", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 11 | "outFiles": ["${workspaceRoot}/client/out/**/*.js"], 12 | "preLaunchTask": { 13 | "type": "npm", 14 | "script": "esbuild" 15 | }, 16 | "env": { 17 | "VSCODE_DEBUG_MODE": "true" 18 | } 19 | }, 20 | ], 21 | } 22 | -------------------------------------------------------------------------------- /vscode/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.openRepositoryInParentFolders": "always", 3 | "cmake.configureOnOpen": false 4 | } -------------------------------------------------------------------------------- /vscode/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "watch", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | }, 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | **/.gitignore 3 | .gitmodules 4 | **/.git 5 | **/.vscode 6 | **/.github 7 | **/*.vsix 8 | **/*.map 9 | **/*.ts 10 | **/README.MD 11 | build_package.sh 12 | **/tsconfig.json 13 | **/tsconfig.base.json 14 | tslint.json 15 | package.json 16 | package-lock.json 17 | .eslintrc.yml 18 | *pygls.log 19 | node_modules/ 20 | **/*.pyc 21 | **/tests/ 22 | **/test_cases/ 23 | .pytest_cache 24 | **/.nox/ 25 | **/noxfile.py 26 | **/requirements.txt 27 | **/requirements.in 28 | **/venv/ 29 | **/__pycache__/ 30 | 31 | !node_modules/@vscode-elements/elements/** 32 | !node_modules/@vscode/codicons/** 33 | !node_modules/axios/** 34 | !node_modules/ejs/** 35 | !node_modules/vscode-languageclient/** 36 | !node_modules/untildify/** 37 | -------------------------------------------------------------------------------- /vscode/COPYRIGHT: -------------------------------------------------------------------------------- 1 | 2 | Most of the files are 3 | 4 | Copyright (c) 2004-2015 Odoo S.A. 5 | 6 | Many files also contain contributions from third 7 | parties. In this case the original copyright of 8 | the contributions can be traced through the 9 | history of the source version control system. 10 | 11 | When that is not the case, the files contain a prominent 12 | notice stating the original copyright and applicable 13 | license, or come with their own dedicated COPYRIGHT 14 | and/or LICENSE file. 15 | -------------------------------------------------------------------------------- /vscode/README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 | 5 |
6 | Visual Studio Extension 7 |
8 |

9 | 10 |

Boost your Odoo code development

11 | 12 | ## About 13 | 14 | This extension integrates the Odoo Language Server, that will help you in the development of your Odoo projects. 15 | 16 | **This project is currently under active development. This is a complex project, and you can encounter various issues, incoherent data or crashes. Do not hesitate to report them to help us build the perfect tool !** 17 | 18 | ## Features 19 | 20 | - Autocompletion 21 | - Simple Autocompletion 22 | ![Autocompletion picture](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/autocomplete.png "Autocompletion") 23 | - Model fields Completion 24 | ![Autocompletion in a loop](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/autocompletion2.png "Autocompletion 2") 25 | - Smart String completion 26 | ![Autocompletion in a decorator](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/autocompletion2.png "Autocompletion 3") 27 | ![Autocompletion in self.env](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/autocompletion3.png "Autocompletion 4") 28 | ![Autocompletion in inverse kwarg](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/autocompletion4.png "Autocompletion 5") 29 | 30 | - Advanced Hover and GoToDefinition 31 | Even on String values! 32 | ![Advanced Hover and GoToDefinition](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/advanced_hover_def.gif "Autocompletion 5") 33 | 34 | - Diagnostics 35 | 36 | ![diagnostics picture](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/diagnostics.png "Diagnostics") 37 | ![diagnostics picture 2](https://raw.githubusercontent.com/odoo/odoo-ls/master/vscode/images/diagnostics2.png "Diagnostics2") 38 | 39 | ## Installation 40 | 41 | ### Requirements 42 | 43 | - Odoo 14+ 44 | - Python 3.8+ 45 | 46 | ### Automatic installation 47 | 48 | Install the extension from the marketplace 49 | - VsCode: [link](https://marketplace.visualstudio.com/items?itemName=Odoo.odoo) 50 | - VsCodium: [link](https://open-vsx.org/extension/Odoo/odoo) 51 | 52 | ### Manually build the .vsix package 53 | 54 | #### Requirements 55 | 56 | - Python 3.8 or greater 57 | - An active virtual environment (`python3 -m venv venv`) 58 | - nox (`pip install nox`) 59 | - node >= 14.19.0 60 | - npm >= 8.3.0 (`npm` is installed with node, check npm version, use `npm install -g npm@8.3.0` to update) 61 | - @vscode/vsce >= 3.2.1 (`npm i -g @vscode/vsce`) 62 | 63 | #### How to bundle into .vsix 64 | 65 | - Activate the nox venv. 66 | - Install nox if not installed yet. 67 | - Run `build_package.sh 68 | ` -------------------------------------------------------------------------------- /vscode/SECURITY.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/SECURITY.md -------------------------------------------------------------------------------- /vscode/build_package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PACKAGE_VERSION=$(cat package.json \ 4 | | grep version \ 5 | | head -1 \ 6 | | awk -F: '{ print $2 }' \ 7 | | sed 's/[",]//g') 8 | echo "detected version: $PACKAGE_VERSION" 9 | middle_version=(${PACKAGE_VERSION//./ }) 10 | if [ $(( ${middle_version[1]} % 2 )) -eq 0 ]; then 11 | echo "pre-release version" 12 | nox --session build_package_prerelease 13 | else 14 | echo "release version" 15 | nox --session build_package 16 | fi 17 | -------------------------------------------------------------------------------- /vscode/client/common/cleanup.mjs: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import * as os from 'os'; 3 | 4 | try { 5 | // Keep as a failsafe for open servers 6 | switch (os.type()) { 7 | case 'Windows_NT': 8 | execSync('taskkill /F /IM odoo_ls_server.exe') 9 | break; 10 | case 'Darwin': 11 | case 'Linux': 12 | execSync("for KILLPID in `ps ax | grep 'odoo_ls_server' | awk ' { print $1;}'`; do kill -15 $KILLPID; done"); 13 | break; 14 | 15 | } 16 | } 17 | catch (err) { 18 | console.log(err) 19 | } -------------------------------------------------------------------------------- /vscode/client/common/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as path from 'path'; 5 | 6 | const folderName = path.basename(__dirname); 7 | export const EXTENSION_ROOT_DIR = 8 | folderName === 'common' ? path.dirname(path.dirname(__dirname)) : path.dirname(__dirname); 9 | export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'server'); 10 | export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, '__main__.py'); 11 | export const DEBUG_SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, `_debug_server.py`); -------------------------------------------------------------------------------- /vscode/client/common/events.ts: -------------------------------------------------------------------------------- 1 | import {EventEmitter} from "vscode"; 2 | 3 | export const clientStopped = new EventEmitter(); 4 | -------------------------------------------------------------------------------- /vscode/client/common/python.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | /* eslint-disable @typescript-eslint/naming-convention */ 5 | import { commands, Disposable, Event, EventEmitter, Uri } from 'vscode'; 6 | import { PythonExtension, ResolvedEnvironment, VersionInfo } from '@vscode/python-extension'; 7 | 8 | export interface IInterpreterDetails { 9 | path?: string[]; 10 | resource?: Uri; 11 | } 12 | 13 | export const onDidChangePythonInterpreterEvent = new EventEmitter(); 14 | 15 | let _api: PythonExtension | undefined; 16 | async function getPythonExtensionAPI(): Promise { 17 | if (_api) { 18 | return _api; 19 | } 20 | _api = await PythonExtension.api(); 21 | return _api; 22 | } 23 | 24 | export async function initializePython(disposables: Disposable[], installListener: boolean = true): Promise { 25 | try { 26 | const api = await getPythonExtensionAPI(); 27 | 28 | if (api && installListener) { 29 | disposables.push( 30 | api.environments.onDidChangeActiveEnvironmentPath((_) => { 31 | onDidChangePythonInterpreterEvent.fire(null); 32 | }), 33 | ); 34 | } 35 | } catch (error) { 36 | console.error('Error initializing python: ', error); 37 | } 38 | } 39 | 40 | export async function resolveInterpreter(interpreter: string[]): Promise { 41 | const api = await getPythonExtensionAPI(); 42 | return api?.environments.resolveEnvironment(interpreter[0]); 43 | } 44 | 45 | export async function getInterpreterDetails(resource?: Uri): Promise { 46 | const api = await getPythonExtensionAPI(); 47 | const environment = await api?.environments.resolveEnvironment( 48 | api?.environments.getActiveEnvironmentPath(resource), 49 | ); 50 | if (environment?.executable.uri && checkVersion(environment)) { 51 | return { path: [environment?.executable.uri.fsPath], resource }; 52 | } 53 | return { path: undefined, resource }; 54 | } 55 | 56 | export async function getDebuggerPath(): Promise { 57 | const api = await getPythonExtensionAPI(); 58 | return api?.debug.getDebuggerPackagePath(); 59 | } 60 | 61 | export async function runPythonExtensionCommand(command: string, ...rest: any[]) { 62 | await getPythonExtensionAPI(); 63 | return await commands.executeCommand(command, ...rest); 64 | } 65 | 66 | export function checkVersion(resolved: ResolvedEnvironment | undefined): boolean { 67 | const version = resolved?.version; 68 | if (version?.major === 3 && version?.minor >= 8) { 69 | return true; 70 | } 71 | console.error(`Python version ${version?.major}.${version?.minor} is not supported.`); 72 | console.error(`Selected python path: ${resolved?.executable.uri?.fsPath}`); 73 | console.error('Supported versions are 3.8 and above.'); 74 | return false; 75 | } 76 | 77 | export async function getPythonVersion(): Promise { 78 | let interpreterDetails = await getInterpreterDetails(); 79 | let resolved = await resolveInterpreter(interpreterDetails.path); 80 | let version = resolved.version; 81 | 82 | return version; 83 | } 84 | -------------------------------------------------------------------------------- /vscode/client/common/safeLanguageClient.d.ts: -------------------------------------------------------------------------------- 1 | import { LanguageClient } from "vscode-languageclient/node"; 2 | 3 | export declare class SafeLanguageClient extends LanguageClient {} 4 | -------------------------------------------------------------------------------- /vscode/client/common/safeLanguageClient.js: -------------------------------------------------------------------------------- 1 | const { LanguageClient, State } = require("vscode-languageclient/node"); 2 | 3 | /** 4 | * A safe extension of LanguageClient that prevents errors when stopping. 5 | */ 6 | class SafeLanguageClient extends LanguageClient { 7 | async sendNotification(method, params) { 8 | try { 9 | await super.sendNotification(method, params); 10 | } catch (error) { 11 | if (this.state == State.Stopped) { 12 | this.info(`Notification ignored: (${method.method}) due to error (${error}) because Client is stopped`); 13 | return; 14 | } 15 | throw error; 16 | } 17 | } 18 | } 19 | 20 | module.exports.SafeLanguageClient = SafeLanguageClient; 21 | -------------------------------------------------------------------------------- /vscode/client/common/validation.ts: -------------------------------------------------------------------------------- 1 | export const STATE_VERSION = 100 2 | 3 | export const stateInit = { 4 | "Odoo.nextConfigId": 0, 5 | "Odoo.stateVersion": STATE_VERSION, 6 | } 7 | 8 | export function getConfigurationStructure(id: number = 0) { 9 | return { 10 | "id": id, 11 | "name": `New Configuration ${id}`, 12 | "odooPath": "", 13 | "addons": [], 14 | "validatedAddonsPaths": [], 15 | "disablePythonLanguageServerPopup": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vscode/client/global.d.ts: -------------------------------------------------------------------------------- 1 | import { OutputChannel, StatusBarItem } from "vscode"; 2 | import { 3 | LanguageClient, 4 | } from "vscode-languageclient/node"; 5 | 6 | declare global { 7 | var LSCLIENT: LanguageClient; 8 | var STATUS_BAR: StatusBarItem; 9 | var OUTPUT_CHANNEL: OutputChannel; 10 | var IS_LOADING: boolean; 11 | var SERVER_PID: number; 12 | var CLIENT_IS_STOPPING: boolean; 13 | var CAN_QUEUE_CONFIG_CHANGE: boolean; 14 | var IS_PYTHON_EXTENSION_READY: boolean; 15 | var PYTHON_EXTENSION_LISTENER_INSTALLED: boolean; 16 | var PATH_VARIABLES: {[id: string] : string}; 17 | } 18 | -------------------------------------------------------------------------------- /vscode/client/migration/migrateConfig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExtensionContext, 3 | workspace, 4 | ConfigurationTarget, 5 | } from "vscode"; 6 | 7 | 8 | export async function migrateConfigToSettings(context: ExtensionContext){ 9 | let oldConfig = context.globalState.get("Odoo.configurations"); 10 | if(oldConfig){ 11 | await context.globalState.update("Odoo.configurations", undefined); // deleting the config in globalStorage 12 | workspace.getConfiguration().update("Odoo.configurations", oldConfig, ConfigurationTarget.Global); // putting it the settings 13 | } 14 | } 15 | export async function migrateAfterDelay(context: ExtensionContext){ 16 | if (String(workspace.getConfiguration().get("Odoo.serverLogLevel")) == "afterDelay"){ 17 | workspace.getConfiguration().update("Odoo.serverLogLevel", "adaptive", ConfigurationTarget.Global) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vscode/client/views/changelog/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Odoo Changelog 8 | 9 | 10 | 11 | <%- content %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /vscode/client/views/changelog/changelogWebview.ts: -------------------------------------------------------------------------------- 1 | import { Disposable, Uri, Webview, WebviewPanel, window } from "vscode"; 2 | import * as vscode from 'vscode'; 3 | import {readFileSync} from 'fs'; 4 | import * as ejs from "ejs"; 5 | import MarkdownIt = require('markdown-it'); 6 | import { getNonce, getUri } from "../../common/utils"; 7 | 8 | const md = new MarkdownIt('commonmark'); 9 | 10 | /** 11 | * This class manages the state and behavior of ConfigurationWebView webview panels. 12 | * 13 | * It contains all the data and methods for: 14 | * 15 | * - Creating and rendering ConfigurationWebView webview panels 16 | * - Properly cleaning up and disposing of webview resources when the panel is closed 17 | * - Setting the HTML (and by proxy CSS/JavaScript) content of the webview panel 18 | * - Setting message listeners so data can be passed between the webview and extension 19 | */ 20 | export class ChangelogWebview { 21 | public static readonly viewType = 'changelogView'; 22 | public static currentPanel: ChangelogWebview | undefined; 23 | private readonly _panel: WebviewPanel; 24 | private _disposables: Disposable[] = []; 25 | private readonly _context: vscode.ExtensionContext 26 | 27 | /** 28 | * The ConfigurationWebView class private constructor (called only from the render method). 29 | * 30 | * @param panel A reference to the webview panel 31 | * @param extensionUri The URI of the directory containing the extension 32 | */ 33 | private constructor(panel: WebviewPanel, context: vscode.ExtensionContext) { 34 | this._panel = panel; 35 | this._context = context; 36 | 37 | // Set an event listener to listen for when the panel is disposed (i.e. when the user closes 38 | // the panel or when the panel is closed programmatically) 39 | this._panel.onDidDispose(this.dispose, this, this._disposables); 40 | 41 | // Set the HTML content for the webview panel 42 | this._panel.webview.html = this._getWebviewContent(this._panel.webview, context.extensionUri); 43 | } 44 | 45 | /** 46 | * Close the current webview panel if one exists then a new webview panel 47 | * will be created and displayed. 48 | * 49 | * @param extensionUri The URI of the directory containing the extension. 50 | */ 51 | public static render(context: vscode.ExtensionContext) { 52 | const column = vscode.window.activeTextEditor 53 | ? vscode.window.activeTextEditor.viewColumn 54 | : undefined; 55 | 56 | // If we already have a panel, close it. 57 | if (ChangelogWebview.currentPanel) { 58 | ChangelogWebview.currentPanel._panel.dispose(); 59 | } 60 | 61 | const panel = window.createWebviewPanel( 62 | // Panel view type 63 | "changelogView", 64 | // Panel title 65 | `Odoo: Changelog`, 66 | // The editor column the panel should be displayed in 67 | column, 68 | // Extra panel configurations 69 | { 70 | // Enable JavaScript in the webview 71 | enableScripts: true, 72 | } 73 | ); 74 | 75 | ChangelogWebview.currentPanel = new ChangelogWebview(panel, context); 76 | } 77 | 78 | 79 | /** 80 | * Cleans up and disposes of webview resources when the webview panel is closed. 81 | */ 82 | public dispose() { 83 | // Dispose of the current webview panel 84 | this._panel.dispose(); 85 | 86 | // Dispose of all disposables (i.e. commands) for the current webview panel 87 | while (this._disposables.length) { 88 | const disposable = this._disposables.pop(); 89 | if (disposable) { 90 | disposable.dispose(); 91 | } 92 | } 93 | } 94 | 95 | 96 | /** 97 | * Defines and returns the HTML that should be rendered within the webview panel. 98 | * 99 | * @param webview A reference to the extension webview 100 | * @param extensionUri The URI of the directory containing the extension 101 | * @returns A template string literal containing the HTML that should be 102 | * rendered within the webview panel 103 | */ 104 | private _getWebviewContent(webview: Webview, extensionUri: Uri) { 105 | // HTML Rendering is done here 106 | const changelogUri = Uri.joinPath(extensionUri, "changelog.md"); 107 | let changelogContent: string = readFileSync(changelogUri.fsPath, 'utf8'); 108 | if (changelogContent === undefined) { 109 | const changelogUri = Uri.joinPath(extensionUri, "changelog.md"); 110 | changelogContent = readFileSync(changelogUri.fsPath, 'utf8'); 111 | } 112 | const htmlPath = getUri(webview, extensionUri, ["client", "views", "changelog", "body.html"]); 113 | const styleUri = getUri(webview, extensionUri, ["client", "views", "changelog", "style.css"]); 114 | const htmlFile = readFileSync(htmlPath.fsPath, 'utf-8'); 115 | 116 | const data = { 117 | styleUri: styleUri, 118 | content: md.render(changelogContent), 119 | cspSource: webview.cspSource, 120 | nonce: getNonce() 121 | } 122 | 123 | return ejs.render(htmlFile, data); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /vscode/client/views/changelog/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-left: auto; 3 | margin-right: auto; 4 | width: 50%; 5 | font-size: 0.95em; 6 | } 7 | 8 | h1, h2 { 9 | padding-bottom: 10px; 10 | } 11 | 12 | body.vscode-dark > :is(h1, h2) { 13 | border-bottom: 1px solid rgba(255,255,255,0.18); 14 | } 15 | 16 | body.vscode-light > :is(h1, h2) { 17 | border-bottom: 1px solid rgba(0,0,0,0.18); 18 | } 19 | -------------------------------------------------------------------------------- /vscode/client/views/configurations/configurationWebView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Odoo Configuration <%= config.id %> 11 | 12 | 13 | 14 |

Odoo Configuration

15 | 16 | 17 | 18 | Configuration name 19 | 20 | 21 | 22 | 23 | 24 | Odoo folder path 25 | 26 |
27 | 28 | 36 | 37 | 38 | 39 | Use ${workspaceFolder} and ${userHome} to make a dynamic configuration 40 | 41 |
42 | 43 | <% if (odooVersion) { %> 44 |

Version <%= odooVersion %>

45 | <% } else if (config.odooPath) { %> 46 |

Not a valid Odoo folder.

47 | <% } %> 48 |
49 |
50 | <% if (!pythonExtensionMode) { %> 51 | 52 | 53 | Path to the Python binary the Language Server will run on 54 | 55 | 56 | 64 | 65 | 66 | 67 | 68 | 69 | <% } %> 70 | 71 | Additional addons 72 | 73 | 74 | 75 | 76 |
77 | 78 | 86 | 87 | 88 |  Add Addons Folder 89 | 90 |
91 |
92 | 93 |
94 |  Save 95 |
96 |
97 |  Delete 98 |
99 |
100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /vscode/client/views/configurations/style.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 1024px) { 2 | body { 3 | padding: 0 8rem; 4 | max-width: 1024px; 5 | margin-right: auto; 6 | } 7 | } 8 | 9 | #config-path-textfield, #config-python-path-textfield { 10 | width: 80%; 11 | } 12 | 13 | #config-addons > vscode-label > label { 14 | padding: 0; 15 | margin-bottom: 2px; 16 | } 17 | 18 | #addons-scrollable { 19 | height: 200px; 20 | width: 100%; 21 | border: 1px solid var(--vscode-input-border); 22 | background-color: var(--vscode-input-background); 23 | box-sizing: border-box; 24 | 25 | } 26 | 27 | #addons-tree { 28 | width: 100%; 29 | color: var(--vscode-input-foreground); 30 | font-family: var(--vscode-font-family); 31 | } 32 | 33 | #add-folder-container { 34 | justify-content: end; 35 | width: 100%; 36 | } 37 | 38 | .button-container { 39 | margin-top: 2px; 40 | display: flex; 41 | box-sizing: border-box; 42 | } 43 | 44 | .button { 45 | display: flex; 46 | box-sizing: border-box; 47 | padding: 0.5em; 48 | user-select: none; 49 | } 50 | 51 | .button:hover { 52 | cursor: pointer; 53 | background-color: var(--vscode-settings-rowHoverBackground); 54 | } 55 | 56 | .button:active { 57 | background-color: var(--vscode-input-background); 58 | } 59 | 60 | #path-helper-valid { 61 | color: green; 62 | } 63 | 64 | #path-helper-invalid { 65 | color: var(--vscode-errorForeground); 66 | } 67 | 68 | .inline-element{ 69 | display: flex; 70 | align-items: center; 71 | gap: 5px; 72 | height: 26px; 73 | } 74 | 75 | .helper { 76 | cursor: pointer; 77 | transition: all 200ms; 78 | height: 100%; 79 | aspect-ratio: 1; 80 | font-size: 18px !important; 81 | display: flex !important; 82 | border-radius: 2px; 83 | justify-content: center; 84 | align-items: center; 85 | } 86 | 87 | .helper:hover { 88 | background: var(--vscode-checkbox-background); 89 | } 90 | 91 | .helper .tooltip{ 92 | font-family: "Droid Sans Mono", "monospace", monospace; 93 | visibility: hidden; 94 | position: absolute; 95 | font-size: var(--vscode-font-size, 13px); 96 | transform: translateY(calc(50% + 18px)); 97 | background: var(--vscode-panel-background); 98 | box-shadow: 0 0 8px 2px var(--vscode-widget-shadow) !important; 99 | padding: 6px 4px; 100 | z-index: 100; 101 | } 102 | 103 | .helper:hover .tooltip{ 104 | visibility: visible; 105 | } 106 | -------------------------------------------------------------------------------- /vscode/client/views/crash_report/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Crash report 11 | 12 | 13 | 14 |

Crash report

15 |

16 | It seems like an unexpected crash happened and halted the execution of the Odoo VSCode extension. 17 |
18 | By sending this crash report you agree to send to the IAP Team the following information. Please review them carefully: 19 |

20 |
    21 |
  • 22 | The content of the document opened when the crash happened. 23 |
  • 24 |
  • 25 | The traceback generated by the crash. 26 |
  • 27 |
  • 28 | Eventual additional information you've provided in this form. 29 |
  • 30 |
31 | 32 | 33 | Crash ID 34 | 35 | You can send this ID to the IAP team when discussing an issue with them. 36 | 37 | 38 | 39 | Crash information 40 | 41 | 42 | 43 | 44 | 45 | Additional information (Optional) 46 | 47 | 48 | 49 | 50 | Send report 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /vscode/client/views/crash_report/script.js: -------------------------------------------------------------------------------- 1 | const vscode = acquireVsCodeApi(); 2 | 3 | window.addEventListener("load", main); 4 | 5 | function main() { 6 | const sendReportButton = document.getElementById('send-report-button'); 7 | 8 | sendReportButton.addEventListener("click", sendCrashReport); 9 | } 10 | 11 | function sendCrashReport() { 12 | vscode.postMessage({ 13 | command: "send_report", 14 | additional_info: document.querySelector('#crash-report-form').data["additional_info"] 15 | }); 16 | } -------------------------------------------------------------------------------- /vscode/client/views/crash_report/style.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 1024px) { 2 | body { 3 | margin-left: 20px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vscode/client/views/welcome/style.css: -------------------------------------------------------------------------------- 1 | #welcome-container { 2 | max-width: 75%; 3 | font-size: 1rem; 4 | margin-left:auto; 5 | margin-right:auto; 6 | display: flex; 7 | justify-content: center; 8 | align-items: flex-start; 9 | flex-direction: column; 10 | } 11 | 12 | .welcome-header { 13 | margin-left:auto; 14 | margin-right:auto; 15 | } 16 | 17 | .img-margins { 18 | margin: 1em; 19 | border: solid black; 20 | } 21 | 22 | #welcome-title { 23 | font-size: 1.3rem; 24 | text-align:center; 25 | } 26 | 27 | li{ 28 | margin: 15px 0; 29 | } 30 | 31 | .alert { 32 | padding: 20px; 33 | font-size: 1.3em; 34 | background-color: #d88629; /* Red */ 35 | color: white; 36 | margin-bottom: 15px; 37 | margin-top: 15px; 38 | } 39 | 40 | #welcome-logo { 41 | width: 200px; 42 | margin-top: 2em 43 | } 44 | -------------------------------------------------------------------------------- /vscode/client/views/welcome/welcomeAlertView.html: -------------------------------------------------------------------------------- 1 |
2 |
WARNING

3 | You are running a pre-release version (<%= version %>) of this extension. Not everything will work as expected.
4 | Thank you for helping us to prepare the best release
5 | Do not hesitate to report any issue on our github: https://github.com/odoo/odoo-ls/issues/new 6 |
-------------------------------------------------------------------------------- /vscode/client/views/welcome/welcomeWebView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |

Welcome to Odoo

18 |
19 | Odoo Language Server will help you to write Odoo modules by providing you with autocompletion, hover, goto definition, code analysis and more. 20 |
21 | <%- alertHTML %> 22 |
23 | >Show Odoo welcome page on startup 24 |
25 |

How to install ?

26 |
27 | If you see this message, it means that your extension is already installed. But to make it shine, it is better to ensure that no other language server is actually running concurrently with this extension. 28 |
29 | The extension is made to work alone or with the Python Extension of VsCode (ms-python.python). If you have another Python language server however, you should probably deactivate it 30 | to avoid double autocompletion and to remove wrong error diagnotics. To do that, you can go in the "Extensions" tab, search for your language server and click on "Disable" or "Disable (workspace)". 31 |
32 | On start-up the extension will prompt you to disable the Language Server from the Python extension, that behavior can be altered in the settings. 33 |
34 |

How to use the extension ?

35 |
36 |

37 | The extension works by setting up a configuration, then analyzing your code with the loaded configuration.
38 | You first have to create the configurations you'll use later. 39 |

40 |

41 | Your available configurations are displayed here:
42 |
43 | Click on it to open the configuration selector panel:
44 |
45 | To create a new configuration, click on "Add a configuration". To edit one, click on the wheel next to the configuration you would like to edit.
46 |
47 |

48 |

49 | Create your configuration with the information related to the odoo environment you want to use:
50 |
51 |

52 |

53 | When your configuration is ready, select it from the panel. This selection will run for the current workspace.
54 | While the wheel next to the Odoo button is spinning, the extension is loading the configuration. When it's done, the wheel will stop spinning and the extension will start to help you in your work.
55 | You can use your vscode while the extension is loading, but not all features will be available until the configuration is ready. 56 |

57 |
58 |

Advanced configuration

59 |
60 |

61 | If you want to edit manually your configurations or script the setup, you can edit them by modifying the settings.json of your 62 | workspace. Be cautious however, as you can break the extension by doing that. Follow carefully the helpers and type annotations. 63 | 64 | You can find more information about settings on our github: https://github.com/odoo/odoo-ls/wiki/Edit-settings.json 65 |

66 |
67 |
68 | 69 | -------------------------------------------------------------------------------- /vscode/client/views/welcome/welcomeWebView.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const vscode = acquireVsCodeApi(); 3 | 4 | window.addEventListener("load", main); 5 | 6 | function main() { 7 | const showWelcomeCheckbox = document.getElementById("displayOdooWelcomeOnStart"); 8 | showWelcomeCheckbox.addEventListener("change", changeDisplayWelcomeValue); 9 | } 10 | 11 | function changeDisplayWelcomeValue() { 12 | vscode.postMessage({ 13 | command: "changeWelcomeDisplayValue", 14 | toggled: document.getElementById("displayOdooWelcomeOnStart").checked 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /vscode/images/advanced_hover_def.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/advanced_hover_def.gif -------------------------------------------------------------------------------- /vscode/images/autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/autocomplete.png -------------------------------------------------------------------------------- /vscode/images/autocompletion2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/autocompletion2.png -------------------------------------------------------------------------------- /vscode/images/autocompletion3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/autocompletion3.png -------------------------------------------------------------------------------- /vscode/images/autocompletion4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/autocompletion4.png -------------------------------------------------------------------------------- /vscode/images/autocompletion5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/autocompletion5.png -------------------------------------------------------------------------------- /vscode/images/diagnostics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/diagnostics.png -------------------------------------------------------------------------------- /vscode/images/diagnostics2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/diagnostics2.png -------------------------------------------------------------------------------- /vscode/images/help_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/help_1.png -------------------------------------------------------------------------------- /vscode/images/help_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/help_2.png -------------------------------------------------------------------------------- /vscode/images/help_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/help_3.png -------------------------------------------------------------------------------- /vscode/images/help_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/help_4.png -------------------------------------------------------------------------------- /vscode/images/odoo_favicon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/odoo_favicon -------------------------------------------------------------------------------- /vscode/images/odoo_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odoo/odoo-ls/fa74ce79cef0d41cc8f9795380f49de9b2f138c3/vscode/images/odoo_logo.png -------------------------------------------------------------------------------- /vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | "lib": ["ES2019","ES2021.String"], 6 | "rootDir": "client/", 7 | "outDir": "client/out", 8 | "sourceMap": true 9 | }, 10 | "include": ["client/**/*.ts", "client/*.ts"], 11 | "exclude": ["node_modules"], 12 | "sourceMaps": true 13 | } 14 | -------------------------------------------------------------------------------- /vscode/vscode.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "../server" 8 | } 9 | ], 10 | "settings": { 11 | "lldb.launch.sourceLanguages": [ 12 | "rust" 13 | ] 14 | } 15 | } --------------------------------------------------------------------------------