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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/actions/test/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Test
description: >
Is this commit good. Nothing is built for release here and nothing is
published; a failure means the code is wrong, not that the pipeline is.

A composite action rather than a reusable workflow so it runs in the caller's
job, under the caller's name -- CI / Test, Release / Test -- rather than as a
nested "caller / callee" check.

ONE definition, called by CI and by Release both. This repository had no CI at
all: release.yml was the only workflow, so nothing checked a commit before it
became a tag other repositories download a binary from.

Assumes Go is already set up; the caller does that, because the toolchain
version is a property of the job rather than of this step.

runs:
using: composite
steps:
- name: Go formatting
shell: bash
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "::error::these files need gofmt:"
echo "$unformatted"
exit 1
fi

- name: Go vet
shell: bash
run: go vet ./...

# There are no _test.go files today, so this reports "no test files" and
# passes. It is here rather than omitted so that the first test written is
# run by CI the moment it lands, without anyone remembering to wire it up.
- name: Go tests
shell: bash
run: go test ./...

- name: Go build
shell: bash
run: go build ./...
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

# Every pull request and every merge to main: test, on one runner.
#
# This repository had NO CI. release.yml was the only workflow, so nothing
# checked a commit before it became a tag that other repositories download a
# binary from -- fieldsofrevik's setup action pulls a gdlint release and puts it
# on PATH.
#
# The push trigger is not redundant: there is no CD here, so nothing else covers
# a merge to main.

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

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
ci:
runs-on: ubuntu-latest
# Bounded, so a step that hangs fails here rather than sitting until the
# runner's own timeout hours later.
timeout-minutes: 20
steps:
# Third-party actions are pinned by SHA, with the tag in a trailing
# comment so the version is still readable.
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod

- name: Test
uses: ./.github/actions/test
7 changes: 5 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ concurrency: release-${{ github.repository }}
jobs:
release:
runs-on: ubuntu-latest
# Bounded, so a step that hangs fails here rather than sitting until the
# runner's own timeout hours later.
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod

Expand Down
50 changes: 25 additions & 25 deletions src/core/entity_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ func NewEntityDetector(file *models.FileInfo) *EntityDetector {
func (d *EntityDetector) DetectEntities() {
lines := strings.Split(d.file.Content, "\n")
d.file.Lines = lines

var currentEnum string
var enumIndent int
inEnum := false
var lastRPCLine int
var className string

for i, line := range lines {
lineNum := i + 1

if matches := patterns.ClassNamePattern.FindStringSubmatch(line); len(matches) > 1 {
className = matches[1]
d.file.ScriptClass = className
Expand All @@ -40,17 +40,17 @@ func (d *EntityDetector) DetectEntities() {
}
d.file.AddEntity(entity)
}

if patterns.RPCPattern.MatchString(line) {
lastRPCLine = lineNum
}

if matches := patterns.FunctionPattern.FindStringSubmatch(line); len(matches) > 2 {
indent := matches[1]
funcName := matches[2]
isStatic := patterns.StaticFuncPattern.MatchString(line)
isRPC := (lastRPCLine == lineNum - 1)
isRPC := (lastRPCLine == lineNum-1)

entity := &models.Entity{
Type: models.EntityFunction,
Name: funcName,
Expand All @@ -62,17 +62,17 @@ func (d *EntityDetector) DetectEntities() {
IsRPC: isRPC,
Parent: className,
}

endLine := d.findFunctionEnd(lines, i, entity.IndentLevel)
entity.EndLine = endLine

d.file.AddEntity(entity)
}

if matches := patterns.SignalPattern.FindStringSubmatch(line); len(matches) > 2 {
indent := matches[1]
signalName := matches[2]

entity := &models.Entity{
Type: models.EntitySignal,
Name: signalName,
Expand All @@ -84,16 +84,16 @@ func (d *EntityDetector) DetectEntities() {
}
d.file.AddEntity(entity)
}

if matches := patterns.ConstantPattern.FindStringSubmatch(line); len(matches) > 2 {
indent := matches[1]
constName := matches[2]

value := ""
if idx := strings.Index(line, "="); idx > 0 {
value = strings.TrimSpace(line[idx+1:])
}

entity := &models.Entity{
Type: models.EntityConstant,
Name: constName,
Expand All @@ -106,11 +106,11 @@ func (d *EntityDetector) DetectEntities() {
}
d.file.AddEntity(entity)
}

if matches := patterns.EnumPattern.FindStringSubmatch(line); len(matches) > 2 {
indent := matches[1]
enumName := matches[2]

entity := &models.Entity{
Type: models.EntityEnum,
Name: enumName,
Expand All @@ -121,26 +121,26 @@ func (d *EntityDetector) DetectEntities() {
Parent: className,
}
d.file.AddEntity(entity)

currentEnum = enumName
enumIndent = patterns.CountIndentLevel(indent)
inEnum = true
}

if inEnum && strings.Contains(line, "}") {
lineIndent := patterns.CountIndentLevel(patterns.ExtractIndentation(line))
if lineIndent <= enumIndent {
inEnum = false
currentEnum = ""
}
}

if inEnum && currentEnum != "" {
trimmed := strings.TrimSpace(line)
if trimmed != "" && trimmed != "{" && trimmed != "}" {
if matches := patterns.EnumValuePattern.FindStringSubmatch(trimmed); len(matches) > 1 {
valueName := matches[1]

entity := &models.Entity{
Type: models.EntityEnumValue,
Name: valueName,
Expand All @@ -160,18 +160,18 @@ func (d *EntityDetector) DetectEntities() {
func (d *EntityDetector) findFunctionEnd(lines []string, startIdx int, funcIndent int) int {
for i := startIdx + 1; i < len(lines); i++ {
line := lines[i]

if patterns.IsEmptyOrComment(line) {
continue
}

indent := patterns.ExtractIndentation(line)
lineIndent := patterns.CountIndentLevel(indent)

if lineIndent <= funcIndent && !patterns.IsEmptyOrComment(line) {
return i
}
}

return len(lines)
}
}
17 changes: 8 additions & 9 deletions src/core/indentation_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,36 @@ func NewIndentationChecker(files []*models.FileInfo) *IndentationChecker {

func (c *IndentationChecker) CheckIndentation() []models.CodeLocation {
var issues []models.CodeLocation

for _, file := range c.files {
fileIssues := c.checkFile(file)
issues = append(issues, fileIssues...)
}

return issues
}

func (c *IndentationChecker) checkFile(file *models.FileInfo) []models.CodeLocation {
var issues []models.CodeLocation

// GDScript should use tabs for indentation
for i, line := range file.Lines {
lineNum := i + 1

// Skip empty lines
if strings.TrimSpace(line) == "" {
continue
}

// Get leading whitespace
trimmed := strings.TrimLeft(line, " \t")
if trimmed == "" {
// Line is all whitespace
continue
}

indent := line[:len(line)-len(trimmed)]

// Check for spaces in indentation
if strings.Contains(indent, " ") {
// Check if it's mixed (both tabs and spaces)
Expand All @@ -67,7 +67,6 @@ func (c *IndentationChecker) checkFile(file *models.FileInfo) []models.CodeLocat
}
}
}

return issues
}

Loading
Loading