Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
fc500b7
feat(typescript): add triple-slash-reference rule implementation
ScriptedAlchemy Sep 12, 2025
a0b83dd
Merge branch 'main' into triple-slash
ScriptedAlchemy Sep 24, 2025
32c9658
fix(triple-slash-reference): report types 'prefer-import' triple-slas…
ScriptedAlchemy Sep 24, 2025
1e2a186
style(triple-slash-reference): gofmt -s the rule file
ScriptedAlchemy Sep 24, 2025
ec436b1
fix(triple-slash-reference): ignore triple-slash inside block comment…
ScriptedAlchemy Sep 24, 2025
c52fd5d
ci: fix golangci-lint config (no duplicate enable/disable); ignore .r…
ScriptedAlchemy Sep 24, 2025
a1fe44a
chore(ci): revert .golangci.yml to match upstream main as requested
ScriptedAlchemy Sep 24, 2025
26c850e
fix(triple-slash-reference): scan only header before first statement;…
ScriptedAlchemy Sep 24, 2025
ac5bc1c
style(triple-slash-reference): gofmt -s
ScriptedAlchemy Sep 24, 2025
6092caa
ci(golangci): remove duplicate disables for depguard/errcheck/paralle…
ScriptedAlchemy Sep 24, 2025
9b54e0d
test: replace triple-slash types with import in rstestEnv.d.ts to sat…
ScriptedAlchemy Sep 25, 2025
b909835
chore(ci): kick test run
ScriptedAlchemy Sep 25, 2025
7648147
fix(ts): triple-slash rule reports header-only and prefer-import imme…
ScriptedAlchemy Sep 25, 2025
3ddce7c
chore(lint): align .golangci.yml with main (disable paralleltest/errc…
ScriptedAlchemy Sep 25, 2025
a343993
fix(spellcheck): rename ctext -> commentText in triple-slash rule; sa…
ScriptedAlchemy Sep 25, 2025
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
7 changes: 4 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ run:

linters:
default: none
# Use disable to turn off linters that are noisy or not yet adopted, per main branch intent
disable:
- depguard
- errcheck
Expand All @@ -18,9 +19,9 @@ linters:
- bodyclose
- canonicalheader
- copyloopvar
- depguard
# - depguard
- durationcheck
- errcheck
# - errcheck
- errchkjson
- errname
- errorlint
Expand All @@ -38,7 +39,7 @@ linters:
- musttag
- nakedret
# - nolintlint
- paralleltest
# - paralleltest
- perfsprint
- predeclared
- reassign
Expand Down
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
typescript-go/
packages/vscode-extension/.vscode-test-out
packages/vscode-extension/.vscode-test
.rslib
packages/rslint-test-tools/tests/typescript-eslint/fixtures
packages/rslint-test-tools/tests/typescript-eslint/rules
packages/rslint/pkg/
website/doc_build
pnpm-lock.yaml
pnpm-lock.yaml
6 changes: 3 additions & 3 deletions cmd/rslint/cmd.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package main

import (
"bufio"
"encoding/json"
"flag"
"bufio"
json "github.com/go-json-experiment/json"
"flag"
"fmt"
"math"
"os"
Expand Down
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/web-infra-dev/rslint
go 1.25.0

replace (
github.com/microsoft/typescript-go/shim/api => ./shim/api
github.com/microsoft/typescript-go/shim/api/encoder => ./shim/api/encoder
github.com/microsoft/typescript-go/shim/ast => ./shim/ast
github.com/microsoft/typescript-go/shim/bundled => ./shim/bundled
github.com/microsoft/typescript-go/shim/checker => ./shim/checker
Expand All @@ -18,13 +20,12 @@ replace (
github.com/microsoft/typescript-go/shim/vfs => ./shim/vfs
github.com/microsoft/typescript-go/shim/vfs/cachedvfs => ./shim/vfs/cachedvfs
github.com/microsoft/typescript-go/shim/vfs/osvfs => ./shim/vfs/osvfs
github.com/microsoft/typescript-go/shim/api => ./shim/api
github.com/microsoft/typescript-go/shim/api/encoder => ./shim/api/encoder
)

require (
github.com/bmatcuk/doublestar/v4 v4.9.1
github.com/fatih/color v1.18.0
github.com/microsoft/typescript-go/shim/api/encoder v0.0.0
github.com/microsoft/typescript-go/shim/ast v0.0.0
github.com/microsoft/typescript-go/shim/bundled v0.0.0
github.com/microsoft/typescript-go/shim/checker v0.0.0
Expand All @@ -40,8 +41,6 @@ require (
github.com/microsoft/typescript-go/shim/vfs v0.0.0
github.com/microsoft/typescript-go/shim/vfs/cachedvfs v0.0.0
github.com/microsoft/typescript-go/shim/vfs/osvfs v0.0.0
github.com/microsoft/typescript-go/shim/api v0.0.0
github.com/microsoft/typescript-go/shim/api/encoder v0.0.0
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a
golang.org/x/sync v0.16.0
golang.org/x/sys v0.35.0
Expand Down
2 changes: 1 addition & 1 deletion internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package ipc
import (
"bufio"
"encoding/binary"
"encoding/json"
json "github.com/go-json-experiment/json"
"errors"
"fmt"
"io"
Expand Down
2 changes: 1 addition & 1 deletion internal/api/api_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ipc

import (
"encoding/json"
json "github.com/go-json-experiment/json"
"testing"
)

Expand Down
14 changes: 8 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package config

import (
"encoding/json"
"fmt"
json "github.com/go-json-experiment/json"
"fmt"
"os"
"path/filepath"
"strings"
Expand All @@ -14,6 +14,7 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/array_type"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/await_thenable"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/class_literal_property_style"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/triple_slash_reference"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_array_delete"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_base_to_string"
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_confusing_void_expression"
Expand Down Expand Up @@ -330,10 +331,11 @@ func RegisterAllRules() {

// registerAllTypeScriptEslintPluginRules registers all available rules in the global registry
func registerAllTypeScriptEslintPluginRules() {
GlobalRuleRegistry.Register("@typescript-eslint/adjacent-overload-signatures", adjacent_overload_signatures.AdjacentOverloadSignaturesRule)
GlobalRuleRegistry.Register("@typescript-eslint/array-type", array_type.ArrayTypeRule)
GlobalRuleRegistry.Register("@typescript-eslint/await-thenable", await_thenable.AwaitThenableRule)
GlobalRuleRegistry.Register("@typescript-eslint/class-literal-property-style", class_literal_property_style.ClassLiteralPropertyStyleRule)
GlobalRuleRegistry.Register("@typescript-eslint/adjacent-overload-signatures", adjacent_overload_signatures.AdjacentOverloadSignaturesRule)
GlobalRuleRegistry.Register("@typescript-eslint/array-type", array_type.ArrayTypeRule)
GlobalRuleRegistry.Register("@typescript-eslint/await-thenable", await_thenable.AwaitThenableRule)
GlobalRuleRegistry.Register("@typescript-eslint/class-literal-property-style", class_literal_property_style.ClassLiteralPropertyStyleRule)
GlobalRuleRegistry.Register("@typescript-eslint/triple-slash-reference", triple_slash_reference.TripleSlashReferenceRule)
GlobalRuleRegistry.Register("@typescript-eslint/dot-notation", dot_notation.DotNotationRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-array-delete", no_array_delete.NoArrayDeleteRule)
GlobalRuleRegistry.Register("@typescript-eslint/no-base-to-string", no_base_to_string.NoBaseToStringRule)
Expand Down
2 changes: 1 addition & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package config

import (
"encoding/json"
json "github.com/go-json-experiment/json"
"testing"
)

Expand Down
14 changes: 9 additions & 5 deletions internal/linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,16 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile

return false
}
file.Node.ForEachChild(childVisitor)
clear(registeredListeners)
}
// Allow rules to handle file-level logic
runListeners(ast.KindSourceFile, &file.Node)
file.Node.ForEachChild(childVisitor)
// Notify exit of the SourceFile after traversing children
runListeners(rule.ListenerOnExit(ast.KindSourceFile), &file.Node)
clear(registeredListeners)
}

}
return lintedFileCount
}
return lintedFileCount
}

type RuleHandler = func(sourceFile *ast.SourceFile) []ConfiguredRule
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package no_confusing_void_expression

import (
"encoding/json"
"github.com/microsoft/typescript-go/shim/ast"
json "github.com/go-json-experiment/json"
"github.com/microsoft/typescript-go/shim/ast"
"github.com/microsoft/typescript-go/shim/checker"
"github.com/microsoft/typescript-go/shim/scanner"
"github.com/web-infra-dev/rslint/internal/rule"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package no_floating_promises

import (
"encoding/json"
json "github.com/go-json-experiment/json"

"github.com/microsoft/typescript-go/shim/ast"
"github.com/microsoft/typescript-go/shim/ast"
"github.com/microsoft/typescript-go/shim/checker"
"github.com/microsoft/typescript-go/shim/scanner"
"github.com/web-infra-dev/rslint/internal/rule"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package triple_slash_reference

import (
"regexp"

"github.com/microsoft/typescript-go/shim/ast"
"github.com/microsoft/typescript-go/shim/core"
"github.com/microsoft/typescript-go/shim/scanner"
"github.com/web-infra-dev/rslint/internal/rule"
)

// maskBlockComments replaces the contents of block comments (/* ... */)
// with spaces, preserving newlines and overall string length so that
// byte offsets remain aligned with the original source.
func maskBlockComments(s string) string {
b := []byte(s)
n := len(b)
for i := 0; i+1 < n; i++ {
if b[i] == '/' && b[i+1] == '*' {
// inside block comment
j := i + 2
for j+1 < n {
// preserve newlines for correct line/column mapping
if b[j] != '\n' && b[j] != '\r' {
b[j] = ' '
}
if b[j] == '*' && b[j+1] == '/' {
// mask the opening and closing too
b[i] = ' '
b[i+1] = ' '
b[j] = ' '
b[j+1] = ' '
i = j + 1
break
}
j++
}
}
}
return string(b)
}

// Note: no per-file state needed beyond immediate reports

func buildMessage(module string) rule.RuleMessage {
return rule.RuleMessage{
Id: "tripleSlashReference",
Description: "Do not use a triple slash reference for " + module + ", use `import` style instead.",
}
}

// Options: { lib?: 'always' | 'never'; path?: 'always' | 'never'; types?: 'always' | 'never' | 'prefer-import' }
type TripleSlashReferenceOptions struct {
Lib string
Path string
Types string
}

// normalizeOptions parses options from either array/object forms and applies defaults
func normalizeOptions(options any) TripleSlashReferenceOptions {
// Defaults from upstream rule
opts := TripleSlashReferenceOptions{
Lib: "always",
Path: "never",
Types: "prefer-import",
}

if options == nil {
return opts
}

// Support array format: [ { ... } ] and object format: { ... }
var m map[string]interface{}
if arr, ok := options.([]interface{}); ok {
if len(arr) > 0 {
if mm, ok := arr[0].(map[string]interface{}); ok {
m = mm
}
}
} else if mm, ok := options.(map[string]interface{}); ok {
m = mm
}

if m == nil {
return opts
}

if v, ok := m["lib"].(string); ok && v != "" {
opts.Lib = v
}
if v, ok := m["path"].(string); ok && v != "" {
opts.Path = v
}
if v, ok := m["types"].(string); ok && v != "" {
opts.Types = v
}
return opts
}

var TripleSlashReferenceRule = rule.CreateRule(rule.Rule{
Name: "triple-slash-reference",
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
opts := normalizeOptions(options)

// Parse header comments once per file
parsed := false

parseHeaderComments := func() {
if parsed {
return
}
parsed = true

// If everything is allowed, do nothing
if opts.Lib == "always" && opts.Path == "always" && opts.Types == "always" {
return
}

sf := ctx.SourceFile
if sf == nil {
return
}

fullText := sf.Text()
// Look only at leading comments before the first token (header area)
start := len(scanner.GetShebang(fullText))
// Match triple-slash reference directives within individual leading comments
lineRe := regexp.MustCompile(`(?m)^[ \t]*///[ \t]*<reference[ \t]*(types|path|lib)[ \t]*=[ \t]*["']([^"']+)["']`)
for comment := range scanner.GetLeadingCommentRanges(&ast.NodeFactory{}, fullText, start) {
// slice the comment text and mask any nested block comments (safety)
commentText := maskBlockComments(fullText[comment.Pos():comment.End()])
if loc := lineRe.FindStringSubmatchIndex(commentText); len(loc) >= 6 {
kind := commentText[loc[2]:loc[3]]
mod := commentText[loc[4]:loc[5]]
// Convert to absolute range in file text by offsetting with comment.Pos()
tr := core.NewTextRange(comment.Pos()+loc[0], comment.Pos()+loc[1])
switch kind {
case "types":
if opts.Types == "never" || opts.Types == "prefer-import" {
ctx.ReportRange(tr, buildMessage(mod))
}
case "path":
if opts.Path == "never" {
ctx.ReportRange(tr, buildMessage(mod))
}
case "lib":
if opts.Lib == "never" {
ctx.ReportRange(tr, buildMessage(mod))
}
}
}
}
}

return rule.RuleListeners{
// Handle file-level triple-slash directives
ast.KindSourceFile: func(node *ast.Node) {
parseHeaderComments()
},
}
},
})
4 changes: 2 additions & 2 deletions internal/utils/jsonc.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package utils

import (
"encoding/json"
json "github.com/go-json-experiment/json"

"github.com/tailscale/hujson"
"github.com/tailscale/hujson"
)

// ParseJSONC parses JSONC (JSON with Comments) using hujson library
Expand Down
Loading
Loading