Linting

The lint package lexes and parses Scriptling source without executing it. It reports syntax errors from the parser; it does not perform semantic analysis, name resolution, style checks, or security analysis. Parsing untrusted text is safe because no script code runs, but a successful result does not make later execution safe.

Basic Linting

import "github.com/paularlott/scriptling/lint"

// Lint inline code
result := lint.Lint(`x = 1 + 2`, nil)
if result.HasIssues() {
    fmt.Println(result.String())
}

Linting Files

// Lint a single file
result, err := lint.LintFile("script.py")
if err != nil {
    log.Fatal(err)
}
if result.HasErrors {
    fmt.Println(result.String())
    os.Exit(1)
}

// Lint multiple files
result, err = lint.LintFiles([]string{"a.py", "b.py", "c.py"})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Checked %d files, found %d issues\n",
    result.FilesChecked, len(result.Errors))

Lint Options

result := lint.Lint(code, &lint.Options{
    Filename: "myscript.py",  // Used for error messages
})

LintError Type

Each issue found is represented as a LintError. The severity constants are part of the result schema, but the current parser-only implementation emits SeverityError with code parse-error; it does not currently produce warning or informational findings:

type LintError struct {
    File     string   // Source file name (may be empty)
    Line     int      // 1-based line number
    Column   int      // 1-based column (0 if not available)
    Message  string   // Human-readable description
    Severity Severity // "error", "warning", or "info"
    Code     string   // Optional error code
}

Result Type

The Result type contains all findings:

type Result struct {
    Errors      []LintError // All issues found
    FilesChecked int        // Number of files linted
    HasErrors   bool       // True if any severity="error" found
}

Processing Lint Errors

result, _ := lint.LintFile("script.py")

for _, err := range result.Errors {
    fmt.Printf("%s:%d: %s (%s)\n",
        err.File, err.Line, err.Message, err.Severity)

    // Check severity
    if err.Severity == lint.SeverityError {
        // Handle errors
    } else if err.Severity == lint.SeverityWarning {
        // Handle warnings
    }
}

// Quick checks
if result.HasErrors {
    fmt.Println("Script has syntax errors")
}
if result.HasIssues() {
    fmt.Println("Script has lint issues")
}

JSON Output

For integration with other tools, you can output results as JSON:

import "encoding/json"

result, _ := lint.LintFile("script.py")
output, _ := json.MarshalIndent(result, "", "  ")
fmt.Println(string(output))

Output:

{
  "errors": [
    {
      "file": "script.py",
      "line": 3,
      "message": "expected token COLON",
      "severity": "error",
      "code": "parse-error"
    }
  ],
  "files_checked": 1,
  "has_errors": true
}

Use Cases

  • Pre-execution syntax validation: Reject source that cannot be parsed
  • CI/CD integration: Fail pipelines on syntax errors
  • Editor integration: Report parser diagnostics without running code
  • Untrusted-source parsing: Inspect syntax without executing the source
// Validate syntax, then execute under the host's normal runtime controls.
func validateAndExecute(p *scriptling.Scriptling, code string) (object.Object, error) {
    result := lint.Lint(code, nil)
    if result.HasErrors {
        return nil, fmt.Errorf("script has syntax errors:\n%s", result.String())
    }

    // A successful parse does not establish that execution is safe.
    return p.Eval(code)
}

Complete Example

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/paularlott/scriptling"
    "github.com/paularlott/scriptling/lint"
    "github.com/paularlott/scriptling/stdlib"
)

func main() {
    // Script to validate and execute
    script := `
def calculate(a, b):
    return a + b

result = calculate(10, 20)
`

    // Step 1: Lint the script
    result := lint.Lint(script, &lint.Options{Filename: "calculator.py"})
    if result.HasErrors {
        fmt.Println("Script has syntax errors:")
        for _, err := range result.Errors {
            fmt.Printf("  %s:%d: %s\n", err.File, err.Line, err.Message)
        }
        os.Exit(1)
    }

    fmt.Printf("Lint passed: %d file(s) checked, %d issue(s) found\n",
        result.FilesChecked, len(result.Errors))

    // Step 2: Execute the script
    p := scriptling.New()
    stdlib.RegisterAll(p)

    _, err := p.Eval(script)
    if err != nil {
        log.Fatal(err)
    }

    // Get the result
    if val, err := p.GetVarAsInt("result"); err == nil {
        fmt.Printf("Result: %d\n", val)
    }
}