Git Product home page Git Product logo

godot-gdscript-toolkit's Introduction

GDScript Toolkit

License: MIT Code style: black

This project provides a set of tools for daily work with GDScript. At the moment it provides:

  • A parser that produces a parse tree for debugging and educational purposes.
  • A linter that performs a static analysis according to some predefined configuration.
  • A formatter that formats the code according to some predefined rules.
  • A code metrics calculator which calculates cyclomatic complexity of functions and classes.

Installation

To install this project you need python3 and pip. Regardless of the target version, installation is done by pip3 command and for stable releases, it downloads the package from PyPI.

Godot 4

pip3 install "gdtoolkit==4.*"

Godot 3

pip3 install "gdtoolkit==3.*"

master (latest)

Latest version (potentially unstable) can be installed directly from git:

pip3 install git+https://github.com/Scony/godot-gdscript-toolkit.git

Linting with gdlint (more)

To run a linter you need to execute gdlint command like:

$ gdlint misc/MarkovianPCG.gd

Which outputs messages like:

misc/MarkovianPCG.gd:96: Error: Function argument name "aOrigin" is not valid (function-argument-name)
misc/MarkovianPCG.gd:96: Error: Function argument name "aPos" is not valid (function-argument-name)

Formatting with gdformat (more)

Formatting may lead to data loss, so it's highly recommended to use it along with Version Control System (VCS) e.g. git

To run a formatter you need to execute gdformat on the file you want to format. So, given a test.gd file:

class X:
	var x=[1,2,{'a':1}]
	var y=[1,2,3,]     # trailing comma
	func foo(a:int,b,c=[1,2,3]):
		if a in c and \
		   b > 100:
			print('foo')
func bar():
	print('bar')

when you execute gdformat test.gd command, the test.gd file will be reformatted as follows:

class X:
	var x = [1, 2, {'a': 1}]
	var y = [
		1,
		2,
		3,
	]  # trailing comma

	func foo(a: int, b, c = [1, 2, 3]):
		if a in c and b > 100:
			print('foo')


func bar():
	print('bar')

Parsing with gdparse (more)

To run a parser you need to execute the gdparse command like:

gdparse tests/valid-gd-scripts/recursive_tool.gd -p

The parser outputs a tree that represents your code's structure:

start
  class_def
    X
    class_body
      tool_stmt
      signal_stmt	sss
  class_def
    Y
    class_body
      tool_stmt
      signal_stmt	sss
  tool_stmt

Calculating cyclomatic complexity with gdradon

To run cyclomatic complexity calculator you need to execute the gdradon command like:

gdradon cc tests/formatter/input-output-pairs/simple-function-statements.in.gd tests/gd2py/input-output-pairs/

The command outputs calculated metrics just like Radon cc command does for Python code:

tests/formatter/input-output-pairs/simple-function-statements.in.gd
    C 1:0 X - A (2)
    F 2:1 foo - A (1)
tests/gd2py/input-output-pairs/class-level-statements.in.gd
    F 22:0 foo - A (1)
    F 24:0 bar - A (1)
    C 18:0 C - A (1)
tests/gd2py/input-output-pairs/func-level-statements.in.gd
    F 1:0 foo - B (8)

Using gdtoolkit's GitHub action

In order to setup a simple action with gdtoolkit's static checks, the base action from this repo can be used:

name: Static checks

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  static-checks:
    name: 'Static checks'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: Scony/godot-gdscript-toolkit@master
    - run: gdformat --check source/
    - run: gdlint source/

See the discussion in #239 for more details.

Development (more)

Everyone is free to fix bugs or introduce new features. For that, however, please refer to existing issue or create one before starting implementation.

godot-gdscript-toolkit's People

Contributors

98teg avatar blankspruce avatar chrisl8 avatar dzil123 avatar f0rsaken avatar holonproduction avatar igordreher avatar levidavidmurray avatar loipesmas avatar lyuma avatar meghprkh avatar micimize avatar mindnect avatar minersebas avatar modprog avatar nathanlovato avatar neilgoodman avatar nonolai avatar o01eg avatar plink-plonk-will avatar razoric480 avatar rcorre avatar rgson avatar scony avatar sh-cho avatar shinigami92 avatar withersail avatar yeslayla avatar ysheng26 avatar yvaucher avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

godot-gdscript-toolkit's Issues

linter: not detecting unused variables

Creating an unused variable in "file.gd" and running gdlint file.gd does not detect the unused variable.

Is it off by default, can it be altered in the gdlintrc file or is it a bug?

Parser error when there's whitespace between : and =

I get a parser error if there's any whitespace after a colon with type inference.

Example:

var is_enabled : = true

Traceback:

expecting: {'_NL', 'TYPE'}

Previous tokens: Token(COLON, ':')


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File /home/gdquest/.local/bin/gdformat, line 10, in <module>
    sys.exit(main())
  File /home/gdquest/.local/lib/python3.7/site-packages/gdtoolkit/formatter/__main__.py, line 102, in main
    raise e
  File /home/gdquest/.local/lib/python3.7/site-packages/gdtoolkit/formatter/__main__.py, line 95, in main
    gdscript_code=code, max_line_length=line_length
  File /home/gdquest/.local/lib/python3.7/site-packages/gdtoolkit/formatter/formatter.py, line 13, in format_code
    parse_tree = parser.parse(gdscript_code, gather_metadata=True)
  File /home/gdquest/.local/lib/python3.7/site-packages/gdtoolkit/parser/parser.py, line 60, in parse
    if gather_metadata
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/lark.py, line 293, in parse
    return self.parser.parse(text, start=start)
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/parser_frontends.py, line 124, in parse
    return self._parse(token_stream, start, set_parser_state)
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/parser_frontends.py, line 54, in _parse
    return self.parser.parse(input, start, *args)
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/parsers/lalr_parser.py, line 35, in parse
    return self.parser.parse(*args)
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/parsers/lalr_parser.py, line 83, in parse
    for token in stream:
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/indenter.py, line 32, in _process
    for token in stream:
  File /home/gdquest/.local/lib/python3.7/site-packages/lark/lexer.py, line 391, in lex
    raise UnexpectedToken(t, e.allowed, state=e.state)
lark.exceptions.UnexpectedToken: Unexpected token Token(EQUAL, '=') at line 11, column 18.
Expected one of: 
* _NL
* TYPE

Working on max-returns lint function

I'll start with a simple function looking at what you did. checking for max returns seems like an easy one.

Do you have any tips to get started? E.g. pretty-printing the parsed syntax tree or something like that to get a sense of how to find the right information.

Linter hangs indefinitely if configuration file is not present

Here's the output after stopping the linter with Ctrl+c:

E:\>gdlint Grid.gd
Traceback (most recent call last):
  File "c:\program files (x86)\python37-32\lib\genericpath.py", line 30, in isfile
    st = os.stat(path)
FileNotFoundError: [WinError 2] The system cannot find the file specified: 'E:\\gdlintrc'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\Scripts\gdlint-script.py", line 11, in <module>
    load_entry_point('gdtoolkit==3.2.2', 'console_scripts', 'gdlint')()
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\linter\__main__.py", line 56, in main
    if os.path.isfile(file_path):
  File "c:\program files (x86)\python37-32\lib\genericpath.py", line 30, in isfile
    st = os.stat(path)
KeyboardInterrupt

This doesn't happen if there's a configuration file in directory where gdlint is executed.

Here's the same output as above plus the configuration file generation:

E:\>gdlint Grid.gd
Traceback (most recent call last):
  File "c:\program files (x86)\python37-32\lib\genericpath.py", line 30, in isfile
    st = os.stat(path)
FileNotFoundError: [WinError 2] The system cannot find the file specified: 'E:\\gdlintrc'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\Scripts\gdlint-script.py", line 11, in <module>
    load_entry_point('gdtoolkit==3.2.2', 'console_scripts', 'gdlint')()
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\linter\__main__.py", line 56, in main
    if os.path.isfile(file_path):
  File "c:\program files (x86)\python37-32\lib\genericpath.py", line 30, in isfile
    st = os.stat(path)
KeyboardInterrupt

E:\>gdlint -d

E:\>gdlint Grid.gd
Grid.gd:2: Error: Definition out of order in global scope (class-definitions-order)
Grid.gd:14: Error: Definition out of order in global scope (class-definitions-order)
Grid.gd:17: Error: Definition out of order in global scope (class-definitions-order)
Grid.gd:18: Error: Definition out of order in global scope (class-definitions-order)
Grid.gd:31: Error: unused function argument 'delta' (unused-argument)

E:\>

Formatter VSCode extension

I coded up a first pass at a VSCode extension that uses the formatter over on this fork.

Would adding this to the official /editor-plugins/ folder be of interest (that is, PRed in) as something you'd want to maintain (Azure account, publishing token, etc,) once it's finished, or should I stick to maintaining it on its own separate repo and act as the extension publisher?

Could we use static type hints?

Python 3.7 and 3.8 now support type hints pretty well, reporting on quite a few errors that linters won't catch otherwise. Also, they make the code more self-documenting. There are functions for which type hints could help a lot when exploring the code, like

def parse(code, gather_metadata=False):

vs

def parse(code: str = "", gather_metadata: bool = False) -> Tree:

In general, without type hints, it takes more time than it could to figure out how the code works for a newcomer like me.

Would you be okay with using type hints?

Removing boilerplate code from the linter

Here's a suggestion to remove the repeated lint function in every linter module and generally remove some code.

This could also help remove the need for the big dictionary you use for the configuration. We could have a json file or a file that users could use for configuration in each project instead.

We can use dir() over modules to get all their attributes and functions, and __dir__() to customize what each module returns. So we can have a function that would:

  • Collect all the linting functions
  • Map them to names
  • Prepare them to run them all in sequence

This would remove the need for manually registering and naming linting functions:

...
("class-definitions-order", partial(_class_definitions_order_check, config["class-definitions-order"]),)
...

We could also generate the default config from these functions. Maybe with annotations, I have to make tests to see what would work best.

Anyway, that would simplify the linter's code and save development time moving forward. May I work on something like that? I'd rather automate stuff like this early on.

Code ordering tool

@Razoric480 contributed a code ordering tool to the VSCode addon, that relies on the formatter. How about having a new formatter option to order the code, so we can have this from the cli and in every code editor? I'd gladly contribute that.

It would also help with performances, as currently in his tool, François has to call gdformat twice.

Suggestions on how to implement it are welcome. I was thinking of formatting first, then reordering the code if the option is in:

  1. having an enum that defines the blocks' order
  2. Parsing the blocks in the current script, storing them as a list of objects with a value from the enum
  3. calling sorted(block_objects, key=operator.attrgetter("value"))
  4. Writing them back to the file

Wildcards don't work on Windows

E:\>gdformat *.gd
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\Scripts\gdformat-script.py", line 11, in <module>
    load_entry_point('gdtoolkit==3.2.4', 'console_scripts', 'gdformat')()
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\formatter\__main__.py", line 91, in main
    with open(file_path, "r+") as fh:
OSError: [Errno 22] Invalid argument: '*.gd'

This is because the Windows console doesn't expand wildcards, but passes them directly to the script.

I'm no Python expert, but looking around I read that the glob module could be used to solve this issue.

I'll also use this issue to thank the developer and contributors of this amazing project!

Add a keyword to detect virtual functions

Virtual and private methods start with one underscore by convention in Godot. Virtual methods should be documented while private methods shouldn't appear in the class reference.

We should have a way to tag or add a keyword to functions so they are treated as virtual

Missing grammar file

After installing gdtoolkit through pip3, I tried formatting a script and got the following error:

E:\>gdformat Grid.gd
exception during formatting of Grid.gd
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\Scripts\gdformat-script.py", line 11, in <module>
    load_entry_point('gdtoolkit==3.2.1', 'console_scripts', 'gdformat')()
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\formatter\__main__.py", line 102, in main
    raise e
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\formatter\__main__.py", line 95, in main
    gdscript_code=code, max_line_length=line_length
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\formatter\formatter.py", line 13, in format_code
    parse_tree = parser.parse(gdscript_code, gather_metadata=True)
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\parser\parser.py", line 60, in parse
    if gather_metadata
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\parser\parser.py", line 36, in __get__
    value = obj.__dict__[self.func.__name__] = self.func(obj)
  File "c:\program files (x86)\python37-32\lib\site-packages\gdtoolkit\parser\parser.py", line 82, in _parser_with_metadata
    maybe_placeholders=False,
  File "c:\program files (x86)\python37-32\lib\site-packages\lark\lark.py", line 266, in open
    with open(grammar_filename, encoding='utf8') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'c:\\program files (x86)\\python37-32\\lib\\site-packages\\gdtoolkit\\parser\\gdscript.lark'

This happend on Windows 10.

Dropping support for Python 3.6?

I saw in the code you wanted to use features from Python 3.7. Is there a particular reason you want to support several versions of Python? Is it for servers or something that'd be on LTS and couldn't upgrade to py 3.7/3.8?

This would give us access to the breakpoint() function, dataclasses, but you could also remove boilerplate code in the linter using module attributes and the __dir__ special function: I think that we can avoid having to maintain the linter features list by hand easily that way.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.