Add snapshot tests to formatter (#545)

* Add snapshot tests for formatter
* Add test runner to CI

---------

Co-authored-by: David Kincaid <daelonsuzuka@gmail.com>
This commit is contained in:
Sandy Gutierrez
2023-12-22 06:04:02 -05:00
committed by GitHub
parent ec1d9c3ae6
commit 0a794ebc1b
43 changed files with 749 additions and 145 deletions

View File

@@ -2,8 +2,12 @@ name: Continuous integration
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
test:
name: Test
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -13,9 +17,32 @@ jobs:
with:
node-version: 16.x
- name: Install dependencies
run: npm install
- name: Run headless test
uses: coactions/setup-xvfb@v1
with:
run: |
npm run compile
npm test
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4.0.0
with:
node-version: 16.x
- name: Install dependencies
run: npm install
- name: Lint and build extension
run: |
npm install
npm run lint
npm run package -- --out godot-tools.vsix
ls -l godot-tools.vsix

View File

@@ -30,6 +30,7 @@ Godot 3.2 or later.
- `ctrl+click` on any symbol to jump to its definition or **open its documentation**
- `ctrl+click` on `res://resource/path` links
- **hover previews on `res://resource/path` links**
- **builtin code formatter**
- autocompletions
- full typed GDScript support
- optional "Smart Mode" to improve productivity with dynamically typed scripts

95
package-lock.json generated
View File

@@ -24,6 +24,7 @@
"ya-bbcode": "^4.0.0"
},
"devDependencies": {
"@types/chai": "^4.3.11",
"@types/marked": "^4.0.8",
"@types/mocha": "^10.0.6",
"@types/node": "^18.15.0",
@@ -36,6 +37,7 @@
"@vscode/test-cli": "^0.0.4",
"@vscode/test-electron": "^2.3.8",
"@vscode/vsce": "^2.21.0",
"chai": "^4.3.10",
"esbuild": "^0.17.15",
"eslint": "^8.37.0",
"mocha": "^10.2.0",
@@ -765,6 +767,12 @@
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"node_modules/@types/chai": {
"version": "4.3.11",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz",
"integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
@@ -1536,6 +1544,15 @@
"node": ">=8"
}
},
"node_modules/assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/await-notify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/await-notify/-/await-notify-1.0.1.tgz",
@@ -1723,6 +1740,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chai": {
"version": "4.3.10",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
"integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==",
"dev": true,
"dependencies": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.3",
"deep-eql": "^4.1.3",
"get-func-name": "^2.0.2",
"loupe": "^2.3.6",
"pathval": "^1.1.1",
"type-detect": "^4.0.8"
},
"engines": {
"node": ">=4"
}
},
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -1737,6 +1772,18 @@
"node": ">=4"
}
},
"node_modules/check-error": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
"integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
"dev": true,
"dependencies": {
"get-func-name": "^2.0.2"
},
"engines": {
"node": "*"
}
},
"node_modules/cheerio": {
"version": "1.0.0-rc.10",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz",
@@ -2020,6 +2067,18 @@
"node": ">=8"
}
},
"node_modules/deep-eql": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
"integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
"dev": true,
"dependencies": {
"type-detect": "^4.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -2804,6 +2863,15 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-func-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
@@ -3448,6 +3516,15 @@
"node": ">=8"
}
},
"node_modules/loupe": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
"integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
"dev": true,
"dependencies": {
"get-func-name": "^2.0.1"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -4053,6 +4130,15 @@
"node": ">=8"
}
},
"node_modules/pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/pause-stream": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
@@ -4866,6 +4952,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",

View File

@@ -802,6 +802,7 @@
}
},
"devDependencies": {
"@types/chai": "^4.3.11",
"@types/marked": "^4.0.8",
"@types/mocha": "^10.0.6",
"@types/node": "^18.15.0",
@@ -814,6 +815,7 @@
"@vscode/test-cli": "^0.0.4",
"@vscode/test-electron": "^2.3.8",
"@vscode/vsce": "^2.21.0",
"chai": "^4.3.10",
"esbuild": "^0.17.15",
"eslint": "^8.37.0",
"mocha": "^10.2.0",

View File

@@ -1,17 +1,41 @@
import * as assert from "assert";
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";
import { format_document } from "./textmate";
import * as chai from "chai";
const expect = chai.expect;
const dots = ["..", "..", ".."];
const basePath = path.join(__filename, ...dots);
suite("GDScript Formatter Tests", () => {
test("Test Formatting", async () => {
const uri = vscode.Uri.file(path.join(basePath, "src/formatter/tests/test1.in.gd"));
const document = await vscode.workspace.openTextDocument(uri);
const edits = format_document(document);
assert.strictEqual(4, edits.length);
// Search for all folders in the snapshots folder and run a test for each
// comparing the output of the formatter with the expected output.
// To add a new test, create a new folder in the snapshots folder
// and add two files, `in.gd` and `out.gd` for the input and expected output.
const snapshotsFolderPath = path.join(basePath, "src/formatter/snapshots");
const testFolders = fs.readdirSync(snapshotsFolderPath);
testFolders.forEach((testFolder) => {
const testFolderPath = path.join(snapshotsFolderPath, testFolder);
if (fs.statSync(testFolderPath).isDirectory()) {
test(`Snapshot Test: ${testFolder}`, async () => {
const uriIn = vscode.Uri.file(path.join(testFolderPath, "in.gd"));
const uriOut = vscode.Uri.file(path.join(testFolderPath, "out.gd"));
const documentIn = await vscode.workspace.openTextDocument(uriIn);
const documentOut = await vscode.workspace.openTextDocument(uriOut);
const edits = format_document(documentIn);
// Apply the formatting edits
const workspaceEdit = new vscode.WorkspaceEdit();
workspaceEdit.set(uriIn, edits);
await vscode.workspace.applyEdit(workspaceEdit);
// Compare the result with the expected output
expect(documentIn.getText()).to.equal(documentOut.getText());
});
}
});
});

View File

@@ -0,0 +1,35 @@
var a = 1
var b = 2
func f():
a = a+b
a = .1+ 2
a= 1. +2
a =1.0 + .2
a = 1.0+ 2.
a =a -b
a = .1- 2
a= 1.-2
a = 1.0 - .2
a =1.0- 2.
a= a/ b
a = .1 /2
a =1. /2
a = 1.0 / .2
a =1.0/2.
a = a *b
a=.1* 2
a = 1. *2
a= 1.0* .2
a =1.0 * 2.
a= a% b
a =1%2
a =-1
a= +1
a = ((-1 + 2) * (3-4) / 5 * 6%( -7 + 8-9 - 10)) * (- 11 + 12) / (13*14 % 15 +16)

View File

@@ -0,0 +1,35 @@
var a = 1
var b = 2
func f():
a = a + b
a = .1 + 2
a = 1. + 2
a = 1.0 + .2
a = 1.0 + 2.
a = a - b
a = .1 - 2
a = 1. - 2
a = 1.0 - .2
a = 1.0 - 2.
a = a / b
a = .1 / 2
a = 1. / 2
a = 1.0 / .2
a = 1.0 / 2.
a = a * b
a = .1 * 2
a = 1. * 2
a = 1.0 * .2
a = 1.0 * 2.
a = a % b
a = 1 % 2
a = -1
a = +1
a = ((-1 + 2) * (3 - 4) / 5 * 6 % (-7 + 8 - 9 - 10)) * (-11 + 12) / (13 * 14 % 15 + 16)

View File

@@ -0,0 +1,10 @@
var primes = [2, 3, 5, 7, 11, 13, 17 ]
var primes2 = [
2 ,
3 ,
5,
7 ,
11 ,
13,
17
]

View File

@@ -0,0 +1,10 @@
var primes = [2, 3, 5, 7, 11, 13, 17]
var primes2 = [
2,
3,
5,
7,
11,
13,
17
]

View File

@@ -0,0 +1,2 @@
func f():
assert (1!= 2)

View File

@@ -0,0 +1,2 @@
func f():
assert(1 != 2)

View File

@@ -0,0 +1,2 @@
func f():
await delay(10)

View File

@@ -0,0 +1,2 @@
func f():
await delay(10)

View File

@@ -0,0 +1,10 @@
func f():
print(not true )
if ( not true) and\
(not true ):
pass
print(not true )
func g():
print(true and ( not false ) or ( true))
print(true and not false or not (true) )

View File

@@ -0,0 +1,10 @@
func f():
print(not true)
if (not true) and \
(not true):
pass
print(not true)
func g():
print(true and (not false) or (true))
print(true and not false or not (true))

View File

@@ -0,0 +1,3 @@
func test():
pass

View File

@@ -0,0 +1,7 @@
enum State { A,B, C }
enum State2 {
A ,
B ,
C
}

View File

@@ -0,0 +1,7 @@
enum State {A, B, C}
enum State2 {
A,
B,
C
}

View File

@@ -0,0 +1,17 @@
func test():
if true:
pass
else:
pass
func test():
if true:
pass
else:
pass
func test():
if true:
pass
else:
pass

View File

@@ -0,0 +1,17 @@
func test():
if true:
pass
else:
pass
func test():
if true:
pass
else:
pass
func test():
if true:
pass
else:
pass

View File

@@ -0,0 +1,15 @@
var a= 10
var b :=10
var c: int = 10
func f(b :=10):
return func(c : = 10):
pass
func f(b : int= 10):
return func(c: int=10 ):
pass
func f( b = 10 ):
return func( c = 10 ):
pass

View File

@@ -0,0 +1,15 @@
var a = 10
var b := 10
var c: int = 10
func f(b:=10):
return func(c:=10):
pass
func f(b: int=10):
return func(c: int=10):
pass
func f(b=10):
return func(c=10):
pass

View File

@@ -0,0 +1,5 @@
func f():
g(func(): return true
h(func():
return false
)

View File

@@ -0,0 +1,5 @@
func f():
g(func(): return true
h(func():
return false
)

View File

@@ -0,0 +1,8 @@
func f():
if a or b\
or c:
pass
if a or \
b or c:
pass

View File

@@ -0,0 +1,8 @@
func f():
if a or b \
or c:
pass
if a or \
b or c:
pass

View File

@@ -0,0 +1,68 @@
@onready var sprite: Sprite2D = %Sprite
@onready var sprites = [ %Sprite1,%Sprite2,%Sprite3 ]
@onready var sprite_name = $Sprite
@onready var sprite_names = [$Sprite1, $Sprite2, $Sprite3]
func f():
print("$Sprite1", $Sprite1)
print("%Sprite1", %Sprite1)
var a=val % otherVal
var a = $Child
var a = $Child/ GrandChild
var a = $Child/ GrandChild / GreatGrandChild
var a = $"../Sibling"
var a = $'../Sibling'
var a = $"../ Sibling "
var a = $' ../Sibling'
var a = $'..' # parent
var a = $"../.." # grandparent
var a = get_node('Child')
var a = get_node("Child/Grand Child")
var a = get_node("../Sibling")
if has_node('Child') and get_node('Child').has_node('GrandChild'):
pass
var a = $%Unique
var a = $Child/%Unique
var a = $Child/ GrandChild/ %Unique
var a = $Child/%Unique/ChildOfUnique
var a = %Unique
var a = %Unique/Child
var a = %Unique/%UniqueChild
var a = $"%Unique"
var a = get_node("%Unique")
var a = NodePath("%Unique")
var a = $'%Unique/Child'
var a = get_node('%Unique/Child')
var a = NodePath('%Unique/Child')
var a = $"%Unique/%UniqueChild"
var a = get_node("%Unique/%Unique Child")
var a = NodePath("%Unique/%Unique Child")
if has_node('%Unique') and get_node('%Child').has_node('%GrandChild'):
pass
var a = $badlyNamedChild
var a = $badlyNamedChild/badly_named_grandchild
var a = NodePath("Child")
var a = NodePath('Child/GrandChild')
var a = NodePath('../Sibling')
var a = get_node("Child").some_method()
var a = get_node("Child/GrandChild").some_method()
var a = get_node("%Child").some_method()
var a = $Child.some_method()
var a = $'Child'.some_method()
var a = $'%Child'.some_method()
var a = $Child/GrandChild.some_method()
var a = $"Child/GrandChild".some_method()
var a = $"%Child/GrandChild".some_method()
var a = $Child.get_node('GrandChild').some_method()
var a = $"Child".get_node('GrandChild').some_method()
var a = $"%Child".get_node('GrandChild').some_method()

View File

@@ -0,0 +1,68 @@
@onready var sprite: Sprite2D = %Sprite
@onready var sprites = [%Sprite1, %Sprite2, %Sprite3]
@onready var sprite_name = $Sprite
@onready var sprite_names = [$Sprite1, $Sprite2, $Sprite3]
func f():
print("$Sprite1", $Sprite1)
print("%Sprite1", %Sprite1)
var a = val % otherVal
var a = $Child
var a = $Child/GrandChild
var a = $Child/GrandChild/GreatGrandChild
var a = $"../Sibling"
var a = $'../Sibling'
var a = $"../ Sibling "
var a = $' ../Sibling'
var a = $'..' # parent
var a = $"../.." # grandparent
var a = get_node('Child')
var a = get_node("Child/Grand Child")
var a = get_node("../Sibling")
if has_node('Child') and get_node('Child').has_node('GrandChild'):
pass
var a = $%Unique
var a = $Child/%Unique
var a = $Child/GrandChild/%Unique
var a = $Child/%Unique/ChildOfUnique
var a = %Unique
var a = %Unique/Child
var a = %Unique/%UniqueChild
var a = $"%Unique"
var a = get_node("%Unique")
var a = NodePath("%Unique")
var a = $'%Unique/Child'
var a = get_node('%Unique/Child')
var a = NodePath('%Unique/Child')
var a = $"%Unique/%UniqueChild"
var a = get_node("%Unique/%Unique Child")
var a = NodePath("%Unique/%Unique Child")
if has_node('%Unique') and get_node('%Child').has_node('%GrandChild'):
pass
var a = $badlyNamedChild
var a = $badlyNamedChild/badly_named_grandchild
var a = NodePath("Child")
var a = NodePath('Child/GrandChild')
var a = NodePath('../Sibling')
var a = get_node("Child").some_method()
var a = get_node("Child/GrandChild").some_method()
var a = get_node("%Child").some_method()
var a = $Child.some_method()
var a = $'Child'.some_method()
var a = $'%Child'.some_method()
var a = $Child/GrandChild.some_method()
var a = $"Child/GrandChild".some_method()
var a = $"%Child/GrandChild".some_method()
var a = $Child.get_node('GrandChild').some_method()
var a = $"Child".get_node('GrandChild').some_method()
var a = $"%Child".get_node('GrandChild').some_method()

View File

@@ -0,0 +1,7 @@
func f():
g(_a_signal_b)
g(_a_await_b)
g(_a_func_b)
g(_a_not_b)
g(_a_true_b)
g(_a_assert_b)

View File

@@ -0,0 +1,7 @@
func f():
g(_a_signal_b)
g(_a_await_b)
g(_a_func_b)
g(_a_not_b)
g(_a_true_b)
g(_a_assert_b)

View File

@@ -0,0 +1,7 @@
func f():
return(some_value)
func g(): return(some_value)
func f():
return func(): return false

View File

@@ -0,0 +1,7 @@
func f():
return (some_value)
func g(): return (some_value)
func f():
return func(): return false

View File

@@ -0,0 +1,8 @@
func f()->bool:
return (some_value)
func g()-> void:
pass
func f() ->void:
return func() -> bool: return false

View File

@@ -0,0 +1,8 @@
func f() -> bool:
return (some_value)
func g() -> void:
pass
func f() -> void:
return func() -> bool: return false

View File

@@ -0,0 +1,3 @@
func f():
print(1);print(2); print(3);
print(4)

View File

@@ -0,0 +1,3 @@
func f():
print(1); print(2); print(3);
print(4)

View File

@@ -0,0 +1,5 @@
func f():
var strings = [", ", "", " ", ", , "]
print("name: %s" % name)
print("%s / %s" % [a, b])
print("%s/%s" % [a, b])

View File

@@ -0,0 +1,5 @@
func f():
var strings = [", ", "", " ", ", , "]
print("name: %s" % name)
print("%s / %s" % [a, b])
print("%s/%s" % [a, b])

View File

@@ -1,5 +0,0 @@
extends Node
func test ():
pass

View File

@@ -3,7 +3,9 @@ import * as fs from "fs";
import * as vsctm from "vscode-textmate";
import * as oniguruma from "vscode-oniguruma";
import { keywords, symbols } from "./symbols";
import { get_extension_uri } from "../utils";
import { get_extension_uri, createLogger } from "../utils";
const log = createLogger("formatter.tm");
// Promisify readFile
function readFile(path) {
@@ -37,13 +39,18 @@ interface Token {
// startIndex: number;
// endIndex: number;
scopes: string[];
original: string;
value: string;
type?: string;
param?: boolean;
string?: boolean;
skip?: boolean;
}
function parse_token(token: Token) {
if (token.scopes.includes("string.quoted.gdscript")) {
token.string = true;
}
if (token.scopes.includes("meta.function.parameters.gdscript")) {
token.param = true;
}
@@ -86,14 +93,19 @@ function between(tokens: Token[], current: number) {
if (prevToken.skip) return "";
if (nextToken.param) {
if (next === "%") return " ";
if (prev === "%") return " ";
if (next === "=") return "";
if (prev === "=") return "";
if (next === ":=") return "";
if (prev === ":=") return "";
if (prevToken?.type === "symbol") return " ";
if (nextToken.type === "symbol") return " ";
}
if (next === ":") {
if (["var", "const"].includes(tokens[current - 2]?.value)) {
if (tokens[current + 1]?.value !== "=") return "";
if (tokens[current + 1]?.value !== "=") return "";
return " ";
}
@@ -101,6 +113,12 @@ function between(tokens: Token[], current: number) {
}
if (prev === "@") return "";
if (prev === "-") {
if (tokens[current - 2]?.value === "(") {
return "";
}
}
if (prev === ":" && next === "=") return "";
if (next === "(") {
if (prev === "export") return "";
@@ -110,13 +128,18 @@ function between(tokens: Token[], current: number) {
if (prev === ")" && nextToken.type === "keyword") return " ";
if (prev === "[" && nextToken.type === "symbol") return "";
if (prev === ":") return " ";
if (prev === ";") return " ";
if (prev === "#") return " ";
if (next === "=") return " ";
if (prev === "=") return " ";
if (tokens[current - 2]?.value === "=") {
if (["+", "-"].includes(prev)) return "";
}
if (prev === "(") return "";
if (next === "{") return " ";
if (next === "\\") return " ";
if (next === "{}") return " ";
if (prevToken?.type === "keyword") return " ";
@@ -141,18 +164,24 @@ export function format_document(document: TextDocument): TextEdit[] {
const edits: TextEdit[] = [];
let lineTokens: vsctm.ITokenizeLineResult = null;
let onlyEmptyLinesSoFar = true;
for (let lineNum = 0; lineNum < document.lineCount; lineNum++) {
const line = document.lineAt(lineNum);
// skip empty lines
if (line.isEmptyOrWhitespace) {
// delete empty lines
if (lineNum === 0 || document.lineAt(lineNum - 1).isEmptyOrWhitespace) {
const range = new Range(lineNum, 0, lineNum + 1, 0);
edits.push(TextEdit.delete(range));
// delete empty lines at the beginning of the file
if (onlyEmptyLinesSoFar) {
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
}
// delete delete the current empty line if the next line is empty too
else if (lineNum < document.lineCount - 1 && document.lineAt(lineNum + 1).isEmptyOrWhitespace) {
edits.push(TextEdit.delete(line.rangeIncludingLineBreak));
}
continue;
}
onlyEmptyLinesSoFar = false;
// skip comments
if (line.text[line.firstNonWhitespaceCharacterIndex] === "#") {
continue;
@@ -171,22 +200,25 @@ export function format_document(document: TextDocument): TextEdit[] {
const tokens: Token[] = [];
for (const t of lineTokens.tokens) {
const value = line.text.slice(t.startIndex, t.endIndex).trim();
// skip whitespace tokens
if (value.trim() === "") {
continue;
}
const token: Token = {
scopes: t.scopes,
value: value,
original: line.text.slice(t.startIndex, t.endIndex),
value: line.text.slice(t.startIndex, t.endIndex).trim(),
};
parse_token(token);
// skip whitespace tokens
if (!token.string && token.value.trim() === "") {
continue;
}
tokens.push(token);
}
for (let i = 0; i < tokens.length; i++) {
nextLine += between(tokens, i) + tokens[i].value;
// log.debug(i, tokens[i].value, tokens[i]);
if (i > 0 && tokens[i - 1].string === true && tokens[i].string === true) {
nextLine += tokens[i].original;
} else {
nextLine += between(tokens, i) + tokens[i].value.trim();
}
}
edits.push(TextEdit.replace(line.range, nextLine));

View File

@@ -3,49 +3,71 @@
"scopeName": "source.gdscript",
"name": "GDScript",
"patterns": [
{ "include": "#nodepath_object" },
{ "include": "#base_expression" },
{ "include": "#logic_op" },
{ "include": "#in_keyword" },
{ "include": "#getter_setter_godot4" },
{ "include": "#compare_op" },
{ "include": "#arithmetic_op" },
{ "include": "#assignment_op" },
{ "include": "#lambda_declaration" },
{ "include": "#control_flow" },
{ "include": "#annotations" },
{ "include": "#keywords" },
{ "include": "#self" },
{ "include": "#class_definition" },
{ "include": "#variable_definition" },
{ "include": "#class_name" },
{ "include": "#builtin_func" },
{ "include": "#builtin_get_node_shorthand" },
{ "include": "#builtin_classes" },
{ "include": "#const_vars" },
{ "include": "#pascal_case_class" },
{ "include": "#class_new" },
{ "include": "#class_is" },
{ "include": "#class_enum" },
{ "include": "#signal_declaration_bare" },
{ "include": "#signal_declaration" },
{ "include": "#function_declaration" },
{ "include": "#function_keyword" },
{ "include": "#any_method" },
{ "include": "#any_variable" },
{ "include": "#any_property" },
{ "include": "#extends" }
{ "include": "#statement" },
{ "include": "#expression" }
],
"repository": {
"statement": {
"patterns": [ { "include": "#extends_statement" } ]
},
"statement_keyword": {
"patterns": [
{
"name": "keyword.control.flow.gdscript",
"match": "(?x)\n \\b(?<!\\.)(\n continue | assert | break | elif | else | if | pass | return | while )\\b\n"
},
{
"name": "storage.type.class.gdscript",
"match": "\\b(?<!\\.)(class)\\b"
},
{
"match": "(?x)\n ^\\s*(\n case | match\n )(?=\\s*([-+\\w\\d(\\[{'\":#]|$))\\b\n",
"captures": { "1": { "name": "keyword.control.flow.gdscript" } }
}
]
},
"extends_statement": {
"match": "(extends)\\s+([a-zA-Z_]\\w*\\.[a-zA-Z_]\\w*)?",
"captures": {
"1": { "name": "keyword.language.gdscript" },
"2": { "name": "entity.other.inherited-class.gdscript" }
}
},
"expression": {
"patterns": [
{ "include": "#base_expression" },
{ "include": "#getter_setter_godot4" },
{ "include": "#assignment_operator" },
{ "include": "#annotations" },
{ "include": "#class_name" },
{ "include": "#builtin_classes" },
{ "include": "#class_new" },
{ "include": "#class_is" },
{ "include": "#class_enum" },
{ "include": "#any_method" },
{ "include": "#any_variable" },
{ "include": "#any_property" }
]
},
"base_expression": {
"patterns": [
{ "include": "#builtin_get_node_shorthand" },
{ "include": "#nodepath_object" },
{ "include": "#nodepath_function" },
{ "include": "#strings" },
{ "include": "#const_vars" },
{ "include": "#keywords" },
{ "include": "#logic_op" },
{ "include": "#logic_operator" },
{ "include": "#compare_operator" },
{ "include": "#arithmetic_operator" },
{ "include": "#lambda_declaration" },
{ "include": "#class_declaration" },
{ "include": "#variable_declaration" },
{ "include": "#signal_declaration_bare" },
{ "include": "#signal_declaration" },
{ "include": "#function_declaration" },
{ "include": "#statement_keyword" },
{ "include": "#assignment_operator" },
{ "include": "#in_keyword" },
{ "include": "#control_flow" },
{ "include": "#round_braces" },
@@ -54,9 +76,7 @@
{ "include": "#self" },
{ "include": "#letter" },
{ "include": "#numbers" },
{ "include": "#builtin_func" },
{ "include": "#builtin_classes" },
{ "include": "#const_vars" },
{ "include": "#pascal_case_class" },
{ "include": "#line_continuation" }
]
@@ -81,7 +101,7 @@
},
"string_formatting": {
"name": "meta.format.percent.gdscript",
"match": "(?x)\n (\n % (\\([\\w\\s]*\\))?\n [-+#0 ]*\n (\\d+|\\*)? (\\.(\\d+|\\*))?\n ([hlL])?\n [diouxXeEfFgGcrsab%]\n )\n",
"match": "(?x)\n (\n % (\\([\\w\\s]*\\))?\n [-+#0 ]*\n (\\d+|\\*)? (\\.(\\d+|\\*))?\n ([hlL])?\n [diouxXeEfFgGcrsab%]\n )\n",
"captures": { "1": { "name": "constant.character.format.placeholder.other.gdscript" } }
},
"nodepath_object": {
@@ -93,7 +113,7 @@
{
"begin": "(\"|')",
"end": "\\1",
"name": "constant.character.escape.gdscript",
"name": "string.quoted.gdscript constant.character.escape.gdscript",
"patterns": [
{
"match": "%",
@@ -115,7 +135,7 @@
{
"begin": "(\"|')",
"end": "\\1",
"name": "meta.literal.nodepath.gdscript constant.character.escape",
"name": "string.quoted.gdscript meta.literal.nodepath.gdscript constant.character.escape",
"patterns": [
{
"match": "%",
@@ -129,7 +149,7 @@
"match": "\\bself\\b",
"name": "variable.language.gdscript"
},
"logic_op": {
"logic_operator": {
"match": "\\b(and|or|not|!)\\b",
"name": "keyword.operator.wordlike.gdscript"
},
@@ -155,15 +175,15 @@
}
]
},
"compare_op": {
"compare_operator": {
"match": "<=|>=|==|<|>|!=",
"name": "keyword.operator.comparison.gdscript"
},
"arithmetic_op": {
"match": "\\+=|-=|\\*=|/=|%=|&=|\\|=|\\*|/|%|\\+|-|<<|>>|&|\\||\\^|~|!",
"arithmetic_operator": {
"match": "->|\\+=|-=|\\*=|/=|%=|&=|\\|=|\\*|/|%|\\+|-|<<|>>|&|\\||\\^|~|!",
"name": "keyword.operator.arithmetic.gdscript"
},
"assignment_op": {
"assignment_operator": {
"match": "=",
"name": "keyword.operator.assignment.gdscript"
},
@@ -172,7 +192,7 @@
"name": "keyword.control.gdscript"
},
"keywords": {
"match": "\\b(?:class|class_name|extends|is|onready|tool|static|export|as|void|enum|preload|assert|breakpoint|rpc|sync|remote|master|puppet|slave|remotesync|mastersync|puppetsync|trait|namespace)\\b",
"match": "\\b(?:class|class_name|is|onready|tool|static|export|as|void|enum|preload|assert|breakpoint|rpc|sync|remote|master|puppet|slave|remotesync|mastersync|puppetsync|trait|namespace)\\b",
"name": "keyword.language.gdscript"
},
"letter": {
@@ -190,32 +210,38 @@
"name": "constant.numeric.integer.hexadecimal.gdscript"
},
{
"match": "[-]?([0-9_]+\\.[0-9_]*(e[\\-\\+]?[0-9_]+)?)",
"match": "[-]?([0-9][0-9_]+\\.[0-9_]*(e[\\-\\+]?[0-9_]+)?)",
"name": "constant.numeric.float.gdscript"
},
{
"match": "[-]?(\\.[0-9_]+(e[\\-\\+]?[0-9_]+)?)",
"match": "[-]?(\\.[0-9][0-9_]*(e[\\-\\+]?[0-9_]+)?)",
"name": "constant.numeric.float.gdscript"
},
{
"match": "[-]?([0-9_]+e[\\-\\+]?\\[0-9_])",
"match": "[-]?([0-9][0-9_]*e[\\-\\+]?\\[0-9_])",
"name": "constant.numeric.float.gdscript"
},
{
"match": "[-]?[0-9_]+",
"match": "[-]?[0-9][0-9_]*",
"name": "constant.numeric.integer.gdscript"
}
]
},
"variable_definition": {
"begin": "\\b(?:(var)|(const))\\s+([a-zA-Z_]\\w*)\\s*",
"end": "$|;",
"variable_declaration": {
"name": "meta.variable.gdscript",
"begin": "\\b(?:(var)|(const))\\s+(?:(\\b[A-Z_][A-Z_0-9]*\\b)|([A-Za-z_]\\w*))\\s*",
"beginCaptures": {
"1": { "name": "keyword.language.gdscript storage.type.var.gdscript" },
"2": { "name": "keyword.language.gdscript storage.type.const.gdscript" },
"3": { "name": "variable.other.gdscript" }
"3": { "name": "constant.language.gdscript" },
"4": { "name": "variable.other.gdscript" }
},
"end": "$|;",
"patterns": [
{
"match": ":=|=(?!=)",
"name": "keyword.operator.assignment.gdscript"
},
{
"match": "(:)\\s*([a-zA-Z_]\\w*)?",
"captures": {
@@ -223,10 +249,6 @@
"2": { "name": "entity.name.type.class.gdscript" }
}
},
{
"match": "=(?!=)",
"name": "keyword.operator.assignment.gdscript"
},
{
"match": "(setget)\\s+([a-zA-Z_]\\w*)(?:[,]\\s*([a-zA-Z_]\\w*))?",
"captures": {
@@ -235,7 +257,7 @@
"3": { "name": "entity.name.function.gdscript" }
}
},
{ "include": "#base_expression" },
{ "include": "#expression" },
{ "include": "#letter" },
{ "include": "#any_variable" },
{ "include": "#any_property" },
@@ -255,19 +277,12 @@
"beginCaptures": { "1": { "name": "entity.name.function.gdscript" } },
"patterns": [
{ "include": "#parameters" },
{ "include": "#line_continuation" },
{
"match": "\\s*(\\-\\>)\\s*([a-zA-Z_]\\w*)\\s*\\:",
"captures": {
"1": { },
"2": { "name": "entity.name.type.class.gdscript" }
}
}
{ "include": "#line_continuation" }
]
}
]
},
"class_definition": {
"class_declaration": {
"match": "(?<=^class)\\s+([a-zA-Z_]\\w*)\\s*(?=:)",
"captures": {
"1": { "name": "entity.name.type.class.gdscript" },
@@ -290,26 +305,18 @@
}
},
"class_enum": {
"match": "\\b([A-Z][a-zA-Z_0-9]*)\\.([A-Z_0-9]+)",
"captures": {
"1": { "name": "entity.name.type.class.gdscript" },
"2": { "name": "constant.language.gdscript" }
},
"match": "\\b([A-Z][a-zA-Z_0-9]*)\\.([A-Z_0-9]+)"
}
},
"class_name": {
"match": "(?<=class_name)\\s+([a-zA-Z_]\\w*(\\.([a-zA-Z_]\\w*))?)",
"captures": {
"1": { "name": "entity.name.type.class.gdscript" },
"2": { "name": "class.other.gdscript" }
},
"match": "(?<=class_name)\\s+([a-zA-Z_]\\w*(\\.([a-zA-Z_]\\w*))?)"
},
"extends": {
"match": "(?<=extends)\\s+[a-zA-Z_]\\w*(\\.([a-zA-Z_]\\w*))?",
"name": "entity.other.inherited-class.gdscript"
},
"builtin_func": {
"match": "(?<![^.]\\.|:)\\b(abs|absf|absi|acos|asin|assert|atan|atan2|bytes2var|bytes2var_with_objects|ceil|char|clamp|clampf|clampi|Color8|convert|cos|cosh|cubic_interpolate|db2linear|decimals|dectime|deg2rad|dict2inst|ease|error_string|exp|floor|fmod|fposmod|funcref|get_stack|hash|inst2dict|instance_from_id|inverse_lerp|is_equal_approx|is_inf|is_instance_id_valid|is_instance_valid|is_nan|is_zero_approx|len|lerp|lerp_angle|linear2db|load|log|max|maxf|maxi|min|minf|mini|move_toward|nearest_po2|pingpong|posmod|pow|preload|print|printerr|printraw|prints|printt|print_debug|print_stack|print_verbose|push_error|push_warning|rad2deg|randf|randfn|randf_range|randi|randi_range|randomize|rand_from_seed|rand_range|rand_seed|range|range_lerp|range_step_decimals|rid_allocate_id|rid_from_int64|round|seed|sign|signf|signi|sin|sinh|smoothstep|snapped|sqrt|stepify|step_decimals|str|str2var|tan|tanh|typeof|type_exists|var2bytes|var2bytes_with_objects|var2str|weakref|wrapf|wrapi|yield)\\b(?=(\\()([^)]*)(\\)))",
"name": "support.function.builtin.gdscript"
}
},
"builtin_get_node_shorthand": {
"patterns": [
@@ -318,7 +325,7 @@
]
},
"builtin_get_node_shorthand_quoted": {
"name": "meta.literal.nodepath.gdscript constant.character.escape.gdscript",
"name": "string.quoted.gdscript meta.literal.nodepath.gdscript constant.character.escape.gdscript",
"begin": "(?:(\\$)|(&|\\^|@))(\"|')",
"beginCaptures": {
"1": { "name": "keyword.control.flow.gdscript" },
@@ -339,10 +346,10 @@
"1": { "name": "keyword.control.flow.gdscript" },
"2": { "name": "constant.character.escape.gdscript" }
},
"end": "[^\\w%]",
"end": "(?!%?\\s*[a-zA-Z_]\\w*)\\s*/?*",
"patterns": [
{
"match": "(%)?([a-zA-Z_]\\w*/?)",
"match": "(%)?\\s*([a-zA-Z_]\\w*)\\s*/?",
"captures": {
"1": { "name": "keyword.control.flow.gdscript" },
"2": { "name": "constant.character.escape.gdscript" }
@@ -370,6 +377,7 @@
"name": "entity.name.type.class.gdscript"
},
"signal_declaration_bare": {
"name": "meta.signal.gdscript",
"match": "(?x) \\s*\n (signal) \\s+\n ([a-zA-Z_]\\w*)(?=[\\n\\s])",
"captures": {
"1": { "name": "keyword.language.gdscript storage.type.function.gdscript" },
@@ -386,34 +394,26 @@
},
"patterns": [
{ "include": "#parameters" },
{ "include": "#line_continuation" },
{
"match": "\\s*(\\-\\>)\\s*([a-zA-Z_]\\w*)\\s*\\:",
"captures": {
"1": { },
"2": { "name": "entity.name.type.class.gdscript" }
}
}
{ "include": "#line_continuation" }
]
},
"lambda_declaration": {
"name": "meta.function.gdscript",
"begin": "(func)\\s?(?=\\()",
"end": "(:|(?=[#'\"\\n]))",
"beginCaptures": {
"1": { "name": "keyword.language.gdscript storage.type.function.gdscript" },
"2": { "name": "entity.name.function.gdscript" }
},
"end": "(:|(?=[#'\"\\n]))",
"end2": "(\\s*(\\-\\>)\\s*(void\\w*)|([a-zA-Z_]\\w*)\\s*\\:)",
"endCaptures2": {
"1": { "name": "punctuation.separator.annotation.result.gdscript" },
"2": { "name": "keyword.language.void.gdscript" },
"3": { "name": "entity.name.type.class.gdscript markup.italic" }
},
"patterns": [
{ "include": "#parameters" },
{ "include": "#line_continuation" },
{
"match": "\\s*(?:\\-\\>)\\s*(void\\w*)|([a-zA-Z_]\\w*)\\s*\\:",
"captures": {
"1": { "name": "keyword.language.void.gdscript" },
"2": { "name": "entity.name.type.class.gdscript" }
}
},
{ "include": "#base_expression" },
{ "include": "#any_variable" },
{ "include": "#any_property" }
@@ -422,29 +422,23 @@
"function_declaration": {
"name": "meta.function.gdscript",
"begin": "(?x) \\s*\n (func) \\s+\n ([a-zA-Z_]\\w*) \\s*\n (?=\\()",
"end": "((:)|(?=[#'\"\\n]))",
"beginCaptures": {
"1": { "name": "keyword.language.gdscript storage.type.function.gdscript" },
"2": { "name": "entity.name.function.gdscript" }
},
"endCaptures": { "1": { "name": "punctuation.section.function.begin.gdscript" } },
"end": "(:|(?=[#'\"\\n]))",
"end2": "(\\s*(\\-\\>)\\s*(void\\w*)|([a-zA-Z_]\\w*)\\s*\\:)",
"endCaptures2": {
"1": { "name": "punctuation.separator.annotation.result.gdscript" },
"2": { "name": "keyword.language.void.gdscript" },
"3": { "name": "entity.name.type.class.gdscript markup.italic" }
},
"patterns": [
{ "include": "#parameters" },
{ "include": "#line_continuation" },
{
"match": "\\s*(?:\\-\\>)\\s*(void\\w*)|([a-zA-Z_]\\w*)\\s*\\:",
"captures": {
"1": { "name": "keyword.language.void.gdscript" },
"2": { "name": "entity.name.type.class.gdscript" }
}
},
{ "include": "#base_expression" }
]
},
"function_keyword": {
"match": "func",
"name": "keyword.language.gdscript"
},
"parameters": {
"name": "meta.function.parameters.gdscript",
"begin": "(\\()",
@@ -472,12 +466,13 @@
"patterns": [ { "include": "#base_expression" } ]
},
"annotated_parameter": {
"begin": "(?x)\n \\b\n ([a-zA-Z_]\\w*) \\s* (:)\n",
"end": "(,)|(?=\\))",
"begin": "(?x)\n \\s* ([a-zA-Z_]\\w*) \\s* (:)\\s* ([a-zA-Z_]\\w*)? \n",
"beginCaptures": {
"1": { "name": "variable.parameter.function.language.gdscript" },
"2": { "name": "punctuation.separator.annotation.gdscript" }
"2": { "name": "punctuation.separator.annotation.gdscript" },
"3": { "name": "entity.name.type.class.builtin.gdscript" }
},
"end": "(,)|(?=\\))",
"endCaptures": { "1": { "name": "punctuation.separator.parameters.gdscript" } },
"patterns": [
{ "include": "#base_expression" },
@@ -523,16 +518,18 @@
"name": "variable.other.gdscript"
},
"any_property": {
"match": "\\b(\\.)\\s*(?<![@\\$#%])([A-Za-z_]\\w*)\\b(?![(])",
"match": "\\b(\\.)\\s*(?<![@\\$#%])(?:(\\b[A-Z_][A-Z_0-9]*\\b)|([A-Za-z_]\\w*))\\b(?![(])",
"captures": {
"1": { "name": "punctuation.accessor.gdscript" },
"2": { "name": "variable.other.property.gdscript" }
"2": { "name": "constant.language.gdscript" },
"3": { "name": "variable.other.property.gdscript" }
}
},
"function_call": {
"name": "meta.function-call.gdscript",
"comment": "Regular function call of the type \"name(args)\"",
"begin": "(?x)\n \\b(?=\n ([a-zA-Z_]\\w*) \\s* (\\()\n )\n",
"beginCaptures": { "2": { "name": "punctuation.definition.arguments.begin.gdscript" } },
"end": "(\\))",
"endCaptures": { "1": { "name": "punctuation.definition.arguments.end.gdscript" } },
"patterns": [
@@ -542,7 +539,6 @@
},
"function_name": {
"patterns": [
{ "include": "#builtin_func" },
{ "include": "#builtin_classes" },
{
"comment": "Some color schemas support meta.function-call.generic scope",

View File

@@ -29,6 +29,15 @@ func remote_function_a():
func remote_function_b():
pass
signal sig_a
signal sig_b()
signal sig_c(param1, param2)
signal sig_d(param1: int, param2: Dictionary)
signal sig_e(
param1: int, # first param
param2: Dictionary,
)
# ------------------------------------------------------------------------------
func f():