Skip to content

Commit 0e03a9c

Browse files
authored
Merge pull request #1863 from ianlewis/1782-feature-support-parsing-stdin
feat: support reading from stdin
2 parents 19119f4 + c76e361 commit 0e03a9c

File tree

15 files changed

+261
-265
lines changed

15 files changed

+261
-265
lines changed

.golangci.yml

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -548,22 +548,36 @@ linters:
548548
exclusions:
549549
# Mode of the generated files analysis.
550550
#
551-
# - `strict`: sources are excluded by strictly following the Go generated file convention.
552-
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
553-
# This line must appear before the first non-comment, non-blank text in the file.
551+
# - `strict`: sources are excluded by strictly following the Go generated
552+
# file convention.
553+
# Source files that have lines matching only the following regular
554+
# expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
555+
# This line must appear before the first non-comment, non-blank text in
556+
# the file.
554557
# https://go.dev/s/generatedcode
555-
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
558+
# - `lax`: sources are excluded if they contain lines like
559+
# `autogenerated file`, `code generated`, `do not edit`, etc.
556560
# - `disable`: disable the generated files exclusion.
557561
#
558562
# Default: strict
559563
generated: lax
560564

561-
# Log a warning if an exclusion rule is unused.
562-
# Default: false
563-
warn-unused: true
564-
565565
# Predefined exclusion rules.
566566
presets:
567567
- common-false-positives
568568
- legacy
569569
- std-error-handling
570+
571+
# Log a warning if an exclusion rule is unused.
572+
# Default: false
573+
warn-unused: true
574+
575+
# Exclude some linters from running on test files.
576+
rules:
577+
- path: _test\.go
578+
linters:
579+
- gocyclo
580+
- cyclop
581+
- errcheck
582+
- dupl
583+
- gosec

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- A new `--language` flag was added to allow users to override the language of
1313
files being scanned ([#1850](https://github.com/ianlewis/todos/issues/1850)).
14+
- Support was added for reading from standard input with the '-' filename
15+
([#1782](https://github.com/ianlewis/todos/issues/1782)).
1416

1517
## [`0.14.0`] - 2025-12-20
1618

cmd/todos/app_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ func Test_TODOsApp_Walk(t *testing.T) {
120120
},
121121
}
122122

123-
d := testutils.NewTempDir(files, nil)
124-
defer d.Cleanup()
123+
d := testutils.NewTempDir(t, files, nil)
125124

126125
app := newTODOsApp()
127126

@@ -152,8 +151,7 @@ func Test_TODOsApp_Walk_no_todos(t *testing.T) {
152151
},
153152
}
154153

155-
d := testutils.NewTempDir(files, nil)
156-
defer d.Cleanup()
154+
d := testutils.NewTempDir(t, files, nil)
157155

158156
app := newTODOsApp()
159157

internal/scanner/scanner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func FromBytes(fileName string, rawContents []byte, lang, charset string) (*Comm
159159
}
160160

161161
if lang == enry.OtherLanguage {
162-
return nil, fmt.Errorf("%w: %s", ErrUnsupportedLanguage, lang)
162+
return nil, fmt.Errorf("%w: %s: language could not be detected", ErrUnsupportedLanguage, fileName)
163163
}
164164

165165
// Detect the language encoding.

internal/scanner/scanner_test.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3878,20 +3878,27 @@ func TestFromFile(t *testing.T) {
38783878
t.Parallel()
38793879
// Create a temporary file.
38803880
// NOTE: File extensions are used as hints so the file name must be part of the suffix.
3881-
f := testutils.Must(os.CreateTemp(t.TempDir(), "*."+testCase.name))
3881+
f, err := os.CreateTemp(t.TempDir(), "*."+testCase.name)
3882+
testutils.Check(t, err)
3883+
38823884
defer f.Close()
38833885

38843886
var w io.Writer
38853887

38863888
w = f
38873889

38883890
if testCase.charset != "" {
3889-
e := testutils.Must(ianaindex.IANA.Encoding(testCase.charset))
3891+
e, encErr := ianaindex.IANA.Encoding(testCase.charset)
3892+
testutils.Check(t, encErr)
3893+
38903894
w = e.NewEncoder().Writer(f)
38913895
}
38923896

3893-
_ = testutils.Must(w.Write(testCase.src))
3894-
_ = testutils.Must(f.Seek(0, io.SeekStart))
3897+
_, err = w.Write(testCase.src)
3898+
testutils.Check(t, err)
3899+
3900+
_, err = f.Seek(0, io.SeekStart)
3901+
testutils.Check(t, err)
38953902

38963903
s, err := FromFile(f, testCase.lang, testCase.scanCharset)
38973904
if diff := cmp.Diff(testCase.err, err, cmpopts.EquateErrors()); diff != "" {
@@ -3922,8 +3929,11 @@ func TestFromBytes(t *testing.T) {
39223929
text := tc.src
39233930

39243931
if tc.charset != "" {
3925-
e := testutils.Must(ianaindex.IANA.Encoding(tc.charset))
3926-
text = testutils.Must(e.NewDecoder().Bytes(tc.src))
3932+
e, err := ianaindex.IANA.Encoding(tc.charset)
3933+
testutils.Check(t, err)
3934+
3935+
text, err = e.NewDecoder().Bytes(tc.src)
3936+
testutils.Check(t, err)
39273937
}
39283938

39293939
s, err := FromBytes(tc.name, text, tc.lang, tc.scanCharset)

internal/testutils/gitrepo.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package testutils
1717
import (
1818
"os"
1919
"path/filepath"
20+
"testing"
2021
"time"
2122

2223
"github.com/go-git/go-git/v5"
@@ -31,14 +32,17 @@ type TestRepo struct {
3132

3233
// NewTestRepo creates a new TestRepo with the given files committed into a
3334
// single commit in the repo.
34-
func NewTestRepo(dir, author, email string, files []*File, links []*Symlink) *TestRepo {
35+
func NewTestRepo(t *testing.T, dir, author, email string, files []*File, links []*Symlink) *TestRepo {
36+
t.Helper()
37+
3538
testRepo := &TestRepo{
3639
dir: dir,
3740
}
41+
repoDir, err := git.PlainInit(testRepo.dir, false)
42+
testRepo.repo = Must(t, repoDir, err)
3843

39-
testRepo.repo = Must(git.PlainInit(testRepo.dir, false))
40-
41-
worktree := Must(testRepo.repo.Worktree())
44+
worktree, err := testRepo.repo.Worktree()
45+
Check(t, err)
4246

4347
const readWriteExec = os.FileMode(0o700)
4448

@@ -47,47 +51,51 @@ func NewTestRepo(dir, author, email string, files []*File, links []*Symlink) *Te
4751
fullPath := filepath.Join(testRepo.dir, f.Path)
4852

4953
// Create necessary sub-directories.
50-
Check(os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
54+
Check(t, os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
5155

5256
// Write the file
53-
Check(os.WriteFile(fullPath, f.Contents, f.Mode))
57+
Check(t, os.WriteFile(fullPath, f.Contents, f.Mode))
5458

5559
// git add <file>
56-
_ = Must(worktree.Add(f.Path))
60+
_, err := worktree.Add(f.Path)
61+
Check(t, err)
5762
}
5863

5964
// git commit <file>
60-
_ = Must(worktree.Commit("add files", &git.CommitOptions{
65+
_, err := worktree.Commit("add files", &git.CommitOptions{
6166
Author: &object.Signature{
6267
Name: author,
6368
Email: email,
6469
When: time.Now(),
6570
},
66-
}))
71+
})
72+
Check(t, err)
6773
}
6874

6975
if len(links) > 0 {
7076
for _, link := range links {
7177
fullPath := filepath.Join(testRepo.dir, link.Path)
7278

7379
// Create necessary sub-directories.
74-
Check(os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
80+
Check(t, os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
7581

7682
// Create the symbolic link.
77-
Check(os.Symlink(link.Target, fullPath))
83+
Check(t, os.Symlink(link.Target, fullPath))
7884

7985
// git add <file>
80-
_ = Must(worktree.Add(link.Path))
86+
_, err := worktree.Add(link.Path)
87+
Check(t, err)
8188
}
8289

8390
// git commit <file>
84-
_ = Must(worktree.Commit("add links", &git.CommitOptions{
91+
_, err := worktree.Commit("add links", &git.CommitOptions{
8592
Author: &object.Signature{
8693
Name: author,
8794
Email: email,
8895
When: time.Now(),
8996
},
90-
}))
97+
})
98+
Check(t, err)
9199
}
92100

93101
return testRepo

internal/testutils/gitrepo_test.go

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,10 @@ func TestTempRepo(t *testing.T) {
3939
t.Parallel()
4040

4141
testCases := map[string]struct {
42-
author string
43-
email string
44-
files []*File
45-
links []*Symlink
46-
expectPanic bool
42+
author string
43+
email string
44+
files []*File
45+
links []*Symlink
4746
}{
4847
"no files": {
4948
author: "John Doe",
@@ -71,18 +70,6 @@ func TestTempRepo(t *testing.T) {
7170
},
7271
},
7372
},
74-
"bad file": {
75-
author: "John Doe",
76-
email: "john@doe.com",
77-
files: []*File{
78-
{
79-
Path: "../../../../../../../testfile.txt",
80-
Contents: []byte("foo"),
81-
Mode: 0o600,
82-
},
83-
},
84-
expectPanic: true,
85-
},
8673
"multi-file": {
8774
author: "John Doe",
8875
email: "john@doe.com",
@@ -154,16 +141,17 @@ func TestTempRepo(t *testing.T) {
154141
t.Run(name, func(t *testing.T) {
155142
t.Parallel()
156143

157-
defer func() {
158-
if r := recover(); r != nil && !testCase.expectPanic {
159-
t.Fatalf("unexpected panic: %v", r)
160-
}
161-
}()
144+
tmpDir := NewTempDir(t, nil, nil)
162145

163-
tmpDir := NewTempDir(nil, nil)
164-
defer tmpDir.Cleanup()
146+
d := NewTestRepo(
147+
t,
148+
tmpDir.Dir(),
149+
testCase.author,
150+
testCase.email,
151+
testCase.files,
152+
testCase.links,
153+
)
165154

166-
d := NewTestRepo(tmpDir.Dir(), testCase.author, testCase.email, testCase.files, testCase.links)
167155
baseDir := d.Dir()
168156

169157
// Check that the temporary directory exists.

internal/testutils/tempdir.go

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package testutils
1717
import (
1818
"os"
1919
"path/filepath"
20+
"testing"
2021
)
2122

2223
// TempDir is a temporary directory which is set up with a directory structure
@@ -49,43 +50,33 @@ type Symlink struct {
4950

5051
// NewTempDir creates a new TempDir. This creates a new temporary directory and
5152
// fills it with the files given. Intermediate directories are created
52-
// automatically with 0700 permissions. This function panics if an error occurs
53-
// when creating the files.
54-
func NewTempDir(files []*File, links []*Symlink) *TempDir {
55-
d := &TempDir{}
53+
// automatically with 0700 permissions. This function calls t.Fatal if an error
54+
// occurs when creating the files.
55+
func NewTempDir(t *testing.T, files []*File, links []*Symlink) *TempDir {
56+
t.Helper()
5657

57-
d.dir = Must(os.MkdirTemp("", "testutils"))
58+
d := &TempDir{}
5859

59-
cleanup, cancel := WithCancel(func() {
60-
d.Cleanup()
61-
}, nil)
62-
defer cleanup()
60+
d.dir = t.TempDir()
6361

6462
const readWriteExec = os.FileMode(0o700)
6563

6664
for _, file := range files {
6765
fullPath := filepath.Join(d.dir, file.Path)
68-
Check(os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
69-
Check(os.WriteFile(fullPath, file.Contents, file.Mode))
66+
Check(t, os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
67+
Check(t, os.WriteFile(fullPath, file.Contents, file.Mode))
7068
}
7169

7270
for _, link := range links {
7371
fullPath := filepath.Join(d.dir, link.Path)
74-
Check(os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
75-
Check(os.Symlink(link.Target, fullPath))
72+
Check(t, os.MkdirAll(filepath.Dir(fullPath), readWriteExec))
73+
Check(t, os.Symlink(link.Target, fullPath))
7674
}
7775

78-
cancel()
79-
8076
return d
8177
}
8278

8379
// Dir returns the path to the directory.
8480
func (d *TempDir) Dir() string {
8581
return d.dir
8682
}
87-
88-
// Cleanup deletes the test directory.
89-
func (d *TempDir) Cleanup() {
90-
Check(os.RemoveAll(d.dir))
91-
}

0 commit comments

Comments
 (0)