Skip to content

Commit 07dfe7a

Browse files
authored
feat(cmd): add the init command for rslint (#250)
1 parent d35c014 commit 07dfe7a

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

GUIDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ yarn add @rslint/core
2020
### Command Line Interface
2121

2222
```bash
23+
# create the default config file
24+
rslint --init
25+
2326
# use default rslint.json
2427
rslint
2528
# use custom configuration file

cmd/rslint/cmd.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ Usage:
314314
rslint [OPTIONS]
315315
316316
Options:
317+
--init Initialize a default config in the current directory.
317318
--config PATH Which rslint config file to use. Defaults to rslint.json.
318319
--format FORMAT Output format: default | jsonline
319320
--fix Automatically fix problems
@@ -328,6 +329,7 @@ func runCMD() int {
328329
flag.Usage = func() { fmt.Fprint(os.Stderr, usage) }
329330

330331
var (
332+
init bool
331333
help bool
332334
config string
333335
fix bool
@@ -343,6 +345,7 @@ func runCMD() int {
343345
)
344346
flag.StringVar(&format, "format", "default", "output format")
345347
flag.StringVar(&config, "config", "", "which rslint config to use")
348+
flag.BoolVar(&init, "init", false, "initialize a default config in the current directory")
346349
flag.BoolVar(&fix, "fix", false, "automatically fix problems")
347350
flag.BoolVar(&help, "help", false, "show help")
348351
flag.BoolVar(&help, "h", false, "show help")
@@ -405,6 +408,14 @@ func runCMD() int {
405408
}
406409
currentDirectory = tspath.NormalizePath(currentDirectory)
407410

411+
if init {
412+
if err := rslintconfig.InitDefaultConfig(currentDirectory); err != nil {
413+
fmt.Fprintf(os.Stderr, "error initializing config: %v\n", err)
414+
return 1
415+
}
416+
return 0
417+
}
418+
408419
fs := bundled.WrapFS(cachedvfs.From(osvfs.FS()))
409420

410421
// Initialize rule registry with all available rules

internal/config/config.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package config
22

33
import (
4+
"fmt"
45
"os"
6+
"path/filepath"
57
"strings"
68

79
"github.com/bmatcuk/doublestar/v4"
@@ -129,6 +131,31 @@ type RuleConfig struct {
129131
Options map[string]interface{} `json:"options,omitempty"` // Rule-specific options
130132
}
131133

134+
const defaultJsonc = `
135+
[
136+
{
137+
// ignore files and folders for linting
138+
"ignores": [],
139+
"languageOptions": {
140+
"parserOptions": {
141+
// Rslint will lint all files included in your typescript projects defined here
142+
// support lint multi packages in monorepo
143+
"project": ["./tsconfig.json"]
144+
}
145+
},
146+
// same configuration as https://typescript-eslint.io/rules/
147+
"rules": {
148+
"@typescript-eslint/require-await": "off",
149+
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
150+
"@typescript-eslint/array-type": ["warn", { "default": "array-simple" }]
151+
},
152+
"plugins": [
153+
"@typescript-eslint" // will enable all implemented @typescript-eslint rules by default
154+
]
155+
}
156+
]
157+
`
158+
132159
// IsEnabled returns true if the rule is enabled (not "off")
133160
func (rc *RuleConfig) IsEnabled() bool {
134161
if rc == nil {
@@ -401,3 +428,21 @@ func isFileIgnoredSimple(filePath string, ignorePatterns []string) bool {
401428
}
402429
return false
403430
}
431+
432+
// initialize a default config in the directory
433+
func InitDefaultConfig(directory string) error {
434+
configPath := filepath.Join(directory, "rslint.jsonc")
435+
436+
// if the config exists
437+
if _, err := os.Stat(configPath); err == nil {
438+
return fmt.Errorf("rslint.json already exists in %s", directory)
439+
}
440+
441+
// write file content
442+
err := os.WriteFile(configPath, []byte(defaultJsonc), 0644)
443+
if err != nil {
444+
return fmt.Errorf("failed to create rslint.json: %w", err)
445+
}
446+
447+
return nil
448+
}

internal/config/config_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package config
22

33
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
47
"testing"
58

69
"github.com/bmatcuk/doublestar/v4"
@@ -491,3 +494,151 @@ func TestGetRulesForFileWithArrayConfig(t *testing.T) {
491494
t.Error("expected rule5 to exist")
492495
}
493496
}
497+
498+
func TestInitDefaultConfig(t *testing.T) {
499+
t.Run("create config in empty directory", func(t *testing.T) {
500+
tempDir := t.TempDir()
501+
502+
err := InitDefaultConfig(tempDir)
503+
if err != nil {
504+
t.Fatalf("InitDefaultConfig failed: %v", err)
505+
}
506+
507+
configPath := filepath.Join(tempDir, "rslint.jsonc")
508+
if _, err := os.Stat(configPath); os.IsNotExist(err) {
509+
t.Error("rslint.jsonc file was not created")
510+
}
511+
512+
content, err := os.ReadFile(configPath)
513+
if err != nil {
514+
t.Fatalf("failed to read created config file: %v", err)
515+
}
516+
517+
if string(content) != defaultJsonc {
518+
t.Error("created config file content does not match expected default content")
519+
}
520+
})
521+
522+
t.Run("fail when config already exists", func(t *testing.T) {
523+
tempDir := t.TempDir()
524+
configPath := filepath.Join(tempDir, "rslint.jsonc")
525+
526+
err := os.WriteFile(configPath, []byte("existing content"), 0644)
527+
if err != nil {
528+
t.Fatalf("failed to create existing config file: %v", err)
529+
}
530+
531+
err = InitDefaultConfig(tempDir)
532+
if err == nil {
533+
t.Error("expected InitDefaultConfig to fail when config already exists")
534+
}
535+
536+
expectedErrorMsg := "rslint.json already exists in " + tempDir
537+
if err.Error() != expectedErrorMsg {
538+
t.Errorf("expected error message %q, got %q", expectedErrorMsg, err.Error())
539+
}
540+
541+
content, err := os.ReadFile(configPath)
542+
if err != nil {
543+
t.Fatalf("failed to read config file: %v", err)
544+
}
545+
546+
if string(content) != "existing content" {
547+
t.Error("existing config file was modified")
548+
}
549+
})
550+
551+
t.Run("fail with invalid directory", func(t *testing.T) {
552+
invalidDir := "/nonexistent/directory/path"
553+
554+
err := InitDefaultConfig(invalidDir)
555+
if err == nil {
556+
t.Error("expected InitDefaultConfig to fail with invalid directory")
557+
}
558+
559+
expectedPrefix := "failed to create rslint.json:"
560+
if !strings.HasPrefix(err.Error(), expectedPrefix) {
561+
t.Errorf("expected error to start with %q, got %q", expectedPrefix, err.Error())
562+
}
563+
})
564+
565+
t.Run("create config with relative path", func(t *testing.T) {
566+
tempDir := t.TempDir()
567+
568+
originalWD, err := os.Getwd()
569+
if err != nil {
570+
t.Fatalf("failed to get current working directory: %v", err)
571+
}
572+
defer func() {
573+
t.Chdir(originalWD)
574+
}()
575+
576+
t.Chdir(tempDir)
577+
578+
err = InitDefaultConfig(".")
579+
if err != nil {
580+
t.Fatalf("InitDefaultConfig with relative path failed: %v", err)
581+
}
582+
583+
configPath := "rslint.jsonc"
584+
if _, err := os.Stat(configPath); os.IsNotExist(err) {
585+
t.Error("rslint.jsonc file was not created with relative path")
586+
}
587+
})
588+
589+
t.Run("create config in nested directory", func(t *testing.T) {
590+
tempDir := t.TempDir()
591+
592+
nestedDir := filepath.Join(tempDir, "project", "config")
593+
err := os.MkdirAll(nestedDir, 0755)
594+
if err != nil {
595+
t.Fatalf("failed to create nested directory: %v", err)
596+
}
597+
598+
err = InitDefaultConfig(nestedDir)
599+
if err != nil {
600+
t.Fatalf("InitDefaultConfig in nested directory failed: %v", err)
601+
}
602+
603+
configPath := filepath.Join(nestedDir, "rslint.jsonc")
604+
if _, err := os.Stat(configPath); os.IsNotExist(err) {
605+
t.Error("rslint.jsonc file was not created in nested directory")
606+
}
607+
})
608+
609+
t.Run("verify config content is valid JSON", func(t *testing.T) {
610+
tempDir := t.TempDir()
611+
612+
err := InitDefaultConfig(tempDir)
613+
if err != nil {
614+
t.Fatalf("InitDefaultConfig failed: %v", err)
615+
}
616+
617+
configPath := filepath.Join(tempDir, "rslint.jsonc")
618+
content, err := os.ReadFile(configPath)
619+
if err != nil {
620+
t.Fatalf("failed to read config file: %v", err)
621+
}
622+
623+
if len(content) == 0 {
624+
t.Error("created config file is empty")
625+
}
626+
627+
contentStr := string(content)
628+
expectedElements := []string{
629+
"ignores",
630+
"languageOptions",
631+
"parserOptions",
632+
"project",
633+
"rules",
634+
"plugins",
635+
"@typescript-eslint",
636+
}
637+
638+
for _, element := range expectedElements {
639+
if !strings.Contains(contentStr, element) {
640+
t.Errorf("config content missing expected element: %s", element)
641+
}
642+
}
643+
})
644+
}

0 commit comments

Comments
 (0)