diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..460d9d1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + open-pull-requests-limit: 10 + schedule: + interval: "monthly" + commit-message: + prefix: "chore" + include: "scope" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..8ce8ce5 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: LintTest + +on: + push: + branches: + - main + pull_request: + +# +# Cancel outdated runs for the same branch / PR +# +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + go: + name: Go LintTest + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set-up Go + uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + args: --verbose + version: v2.6.1 + + - name: go-test + shell: bash + run: go test ./... -v diff --git a/.gitignore b/.gitignore index 8cda725..66874fb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ refdir !analysis/refdir +ignored.txt +godepgraph.png \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..07d55c4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,443 @@ +# All linters are checked and included either in enable or disable as of v2.6.0 of golangci-lint. +# gofumpt and golines are used as formatters. +# This is the config file for lint task of this library. +version: "2" +run: + modules-download-mode: readonly +linters: + default: none + enable: + # Check for pass []any as any in variadic func(...any). Rare case but good to have. + - asasalint + + # Use plain ASCII identifiers. + - asciicheck + + # Checks for dangerous unicode character sequences. Rare case but good to have. + - bidichk + + # Checks whether HTTP response body is closed successfully. + - bodyclose + + # Check whether the function uses a non-inherited context. + - contextcheck + + # Find duplicate words. Rare case but good to have. + - dupword + + # Check for two durations multiplied together. + - durationcheck + + # Check if embedded fields are at top of the struct. Mutex should not be embedded. + - embeddedstructfieldcheck + + # Forces to not skip error check. + - errcheck + + # Checks `Err-` prefix for var and `-Error` suffix for error type. + - errname + + # Suggests to use `%w` for error-wrapping and other post 1.13 directives of Is, As etc. + - errorlint + + # Forces to handle more cases in enums. + - exhaustive + + # Detects functions from golang.org/x/exp/ that can be replaced by standard functions. + # I don't prefer using exp at all, but good to keep it for preventing any accidents. + - exptostd + + # Detects nested contexts in loops and function literals. + - fatcontext + + # Finds forced type assertions. + - forcetypeassert + + # Check order of functions and constructors. + - funcorder + + # Checks that compiler directive comments (//go:) are valid. + - gocheckcompilerdirectives + + # Run exhaustiveness checks on Go "sum types". + - gochecksumtype + + # Finds repeated strings that could be replaced by a constant. + - goconst + + # Meta linter with lots of great checks + - gocritic + + # Forces to put `.` at the end of the comment. Code is poetry. + - godot + + # Documentation sanity. + - godoclint + + # Powerful security-oriented linter. But requires some time to + # configure it properly, see https://github.com/securego/gosec#available-rules + - gosec + + # Official Go tool. Must have. + - govet + + # Checks import alias consistency. + - importas + + # Reports interfaces with unnamed method parameters. + - inamedparam + + # Detects when assignments to existing variables are not used. + - ineffassign + + # Dont allow non iota constants mixed with iota. + - iotamixing + + # Reports wrong mirror patterns of bytes/strings usage. + - mirror + + # Fix all the misspells, amazing thing. + - misspell + + # gopls modernize analyzers + - modernize + + # Enforce tags in un/marshaled structs. + - musttag + + # Finds naked/bare returns and requires change them. + - nakedret + + # Require a bit more explicit returns. Finds the code that returns nil even if it checks that the error is not nil. + - nilerr + + # Require a bit more explicit returns. Checks that there is no simultaneous return of nil error and an invalid value. + - nilnil + + # Finds sending HTTP request without context.Context. + - noctx + + # Forces comment why another check is disabled. + # Better not to have //nolint: at all ;) + - nolintlint + + # Checks that fmt.Sprintf can be replaced with a faster alternative. + - perfsprint + + # Finds slices that could potentially be pre-allocated. + # Small performance win + cleaner code. + - prealloc + + # Finds shadowing of Go's predeclared identifiers. + # I hear a lot of complaints from junior developers. + # But after some time they find it very useful. + - predeclared + + # Checks that package variables are not reassigned. + # Super rare case but can catch bad things (like `io.EOF = nil`) + - reassign + + # Checks for receiver type consistency. Dont mix and match value and pointer receivers. + - recvcheck + + # Drop-in replacement of `golint`. + - revive + + # Somewhat similar to `bodyclose` but for `database/sql` package. + - rowserrcheck + + # Ensures consistent code style when using log/slog. + - sloglint + + # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed. + - sqlclosecheck + + # Meta linter: golangci-lint runs all SA checks from staticcheck via this lint config + - staticcheck + + # Makes you add t.Helper() as the first line in test helpers. + - thelper + + # Reports uses of functions with replacements inside the testing package. + - tparallel + + # Remove unnecessary type conversions, make code cleaner + - unconvert + + # Checks Go code for unused constants, variables, functions and types. + - unused + + # Detect the possibility to use variables/constants from stdlib. + - usestdlibvars + + # Reports uses of functions with replacement inside the testing package. + - usetesting + + # Finds wasted assignment statements. + - wastedassign + + disable: + # Linter for ArangoDB go driver + - arangolint + + # Checks whether net/http.Header uses canonical header. Most probably good to have but not my use case + - canonicalheader + + # Detects struct containing context.Context field. Not a problem. + - containedctx + + # Detect if loop vars are copied. checked by modernize too. + - copyloopvar + + # Checks function and package cyclomatic complexity. + # I can have a long but trivial switch-case. + # + # Cyclomatic complexity is a measurement, not a goal. + # (c) Bryan C. Mills / https://github.com/bcmills + - cyclop + + # Check declaration order of types, constants, variables, and functions. + # I can have mixed things, like interface type and then its funcs and then other interface with its funcs etc. + - decorder + + # Checks if package imports are in a list of acceptable packages. + # I'm very picky about what I import, so no automation. + - depguard + + # Checks assignments with too many blank identifiers. I amy have this as a conscious choice, in tests etc. + - dogsled + + # Tool for code clone detection. + - dupl + + # Error lint covers all things from this and more. + - err113 + + # Checks if err from json functions can be ignored. I'm fine to check the error from json.Marshal. + - errchkjson + + # Forces to initialize all fields of struct. Lots of issues in linter wrt empty struct, private fields etc. + - exhaustruct + + # Forbids some identifiers. I don't have a case for it. + - forbidigo + + # Function length checker. I might have long but a simple function. + - funlen + + # only if ginkgo and gomega packages are used. + - ginkgolinter + + # Check that no global variables exist. Globals are okay. + - gochecknoglobals + + # Checks that no init functions are present in Go code. init() is okay. + - gochecknoinits + + # Similar to cyclop linter (see above). Computes and checks the cognitive complexity of functions. + - gocognit + + # Similar to cyclop linter (see above). Computes and checks the cognitive complexity of functions. + - gocyclo + + # TODO and friends are okay. + - godox + + # I don't use file headers. + - goheader + + # Manages the use of replace, retract, and exclude directives in go.mod. + - gomoddirectives + + # Allowed/blocked packages to import. I prefer to do it manually. + - gomodguard + + # Printf-like functions must have 'f' at the end. Not useful to me. + - goprintffuncname + + # Reports certain internationalization anti-patterns. + - gosmopolitan + + # Group declarations, for import, const, var, etc. Dont have a need for it. + - grouper + + # Detects the incorrect use of interfaces to avoid interface pollution. Very subjective. + - iface + + # Forces tiny interfaces, very subjective. + - interfacebloat + + # Finds places where for-loops could use an integer range. modernize also does it. + - intrange + + # Accept interfaces, return concrete types. Not always. + - ireturn + + # Reports long lines. + # Using golines with gofumpt as base formatter takes care of this. + # This is not really AST-based, so it has issues with long struct tags, etc. + - lll + + # Some log checkers might be useful. I tend to use log/slog. govet checks the same thing for it post 1.21. + - loggercheck + + # Maintainability index of each function, subjective. + - maintidx + + # Slice declarations with non-zero initial length. + - makezero + + # Detects magic numbers. I dont mind in some cases. + - mnd + + # Deeply nested if statements, subjective. + - nestif + + # Reports constructs that check 'err != nil' but return a different nil value error. + # nilerr covers the desired things. + - nilnesserr + + # Checks for a new line before return and branch statements to increase code clarity. Subjective. + - nlreturn + + # Detect inline err handling. I prefer inline err handling if possible. + - noinlineerr + + # Reports all named returns. Direct conflict with: unnamedResult from go-critic. + - nonamedreturns + + # Finds misuse of Sprintf with host:port in a URL. Cool but rare. + - nosprintfhostport + + # I don't use t.Parallel() that much. + - paralleltest + + # Lint your Prometheus metrics name. + - promlinter + + # Reports direct reads from proto message fields when getters should be used. + - protogetter + + # Checks for mistakes with OpenTelemetry/Census spans. + - spancheck + + # Checks that struct tags are well aligned. + - tagalign + + # Checks the struct tags. + - tagliatelle + + # Expects testable examples. + - testableexamples + + # Checks usage of github.com/stretchr/testify. + - testifylint + + # Forces the use of a separate _test package. Often non-_test package is okay. + - testpackage + + # Reports unused function parameters. Covered by revive. + - unparam + + # Avoid select * in SQL query. + - unqueryvet + + # I'm fine with long variable names with a small scope. + - varnamelen + + # Checks for unnecessary newlines at the start and end of functions. + # gofumpt covers that. + - whitespace + + # Wrap all errors from external packages. + - wrapcheck + + # Forces you to use empty lines. + - wsl_v5 + + # Detects the wrong usage of zerolog where a user forgets to dispatch with Send or Msg. + - zerologlint + + settings: + embeddedstructfieldcheck: + # Checks that sync.Mutex and sync.RWMutex are not used as embedded fields. + forbid-mutex: true + + errcheck: + # Report `a := b.(MyStruct)` when `a, ok := ...` should be. + check-type-assertions: true # Default: false + + gocritic: + # As of golangci-lint v2.0.2 this enabled 102 checks and disables below 4. + enable-all: true + disabled-checks: + # These will detect many cases, but they do make sense + # if it's performance oriented code + - hugeParam + - rangeExprCopy + - rangeValCopy + - commentedOutCode + + godot: + # Comments to be checked: `declarations`, `toplevel`, `noinline` or `all`. + # Default: declarations + scope: noinline + capital: true + + importas: + # Do not allow unaliased imports of aliased packages. + # https://github.com/julz/importas/issues/15 + # no-unaliased: true + + # Do not allow non-required aliases. + no-extra-aliases: true + + nakedret: + # No naked returns, ever. + max-func-lines: 0 # Default: 30 + + revive: + rules: + - name: unused-parameter + # Too noisy + disabled: true + + sloglint: + msg-style: "lowercased" + # Doesnt support things like bundleID, can enable and just check for discovery, once in a while. + # key-naming-case: "camel" + + exclusions: + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + # Which file paths to exclude: they will be analyzed, but issues from them won't be reported. + # "/" will be replaced by the current OS file path separator to properly work on Windows. + # Default: [] + paths: + - third_party$ + - builtin$ + rules: + - text: '(slog|log)\.\w+' + linters: + - noctx +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +formatters: + enable: + - gofumpt + - golines + exclusions: + paths: + - third_party$ + - builtin$ + settings: + gofumpt: + extra-rules: true + golines: + max-len: 120 + shorten-comments: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..bf43ba4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "golang.go", // Go + "yzhang.markdown-all-in-one", // Markdown All in One + "davidanson.vscode-markdownlint", + "esbenp.prettier-vscode", // Prettier + "redhat.vscode-yaml", // YAML + "redhat.vscode-xml", // XML + "foxundermoon.shell-format" // Shell + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..91be908 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch test function", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${workspaceFolder}", + "args": ["-test.run", "MyTestFunction"] + }, + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5d04f6d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,63 @@ +{ + // ============================================ + // General Editor Settings + // ============================================ + "editor.formatOnSave": true, + "editor.rulers": [120], + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "editor.linkedEditing": true, + "editor.quickSuggestions": { + "strings": "on" + }, + "search.exclude": { + "**/.git": true, + "**/.gocache": true, + "**/out": true, + "**/dist": true, + "**/build/bin": true + }, + + // ============================================ + // Language-Specific Formatter Overrides + // ============================================ + "[yaml]": { + // Note that this wont be used for github workflow yml files as it doesnt support it + "editor.defaultFormatter": "redhat.vscode-yaml" + }, + "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[shellscript]": { "editor.defaultFormatter": "foxundermoon.shell-format" }, + + "[go]": { + "editor.defaultFormatter": "golang.go", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + + // --- Go --- + "go.lintTool": "golangci-lint", + "go.formatTool": "custom", + "go.alternateTools": { + "customFormatter": "golangci-lint" + }, + "go.formatFlags": ["fmt", "--stdin"], + "go.lintOnSave": "package", + "go.testFlags": ["-timeout=30m", "-count=1", "-v"], + "go.toolsManagement.autoUpdate": false, + "go.addTags": { + "transform": "camelcase" + }, + + // --- Markdown --- + "markdown.extension.toc.levels": "2..4", + "markdownlint.config": { + "MD030": false, + "MD033": { + "allowed_elements": ["table", "td", "tr", "strong", "details", "summary"] + } + } +} diff --git a/README.md b/README.md index 00f2b21..76ccea3 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,101 @@ -## refdir — Go linter that can enforce reference-based ordering of definitions in a file +# Refdir - Go linter that can enforce reference-based ordering of definitions in a _file_ -[![Go Report Card](https://goreportcard.com/badge/github.com/devnev/refdir)](https://goreportcard.com/report/github.com/devnev/refdir) +[![Go Report Card](https://goreportcard.com/badge/github.com/ppipada/refdir)](https://goreportcard.com/report/github.com/ppipada/refdir) -This linter was start as a fork from the [vertfn linter by -@nikolaydubina](https://github.com/nikolaydubina/vertfn). However it is a -significant departure in scope from that linter. +This linter is a maintained fork of the [refdir linter by @devnev](https://github.com/devnev/refdir). It has the below bug fixes/enhancements as of November 2025: -**Disclaimer**: false positives; practically this is useful for "exploration" rather than for "enforcement" +- Don't report recursive functions as an issue. Original [issue](https://github.com/devnev/refdir/issues/10) with [PR](https://github.com/devnev/refdir/pull/11) +- Respect Ignore checks. +- Interface selections are treated as type references rather than function references. Avoids logical contradiction wrt interface type definition and reference inside same file. +- Lesser noise for universal scope identifiers. +- Working `golangci-lint` custom module plugin for version > 2. +- Chores: Stricter `golangci-lint` config compliant code; `taskfile.dev` tasks; github action integration, vscode settings folders, updated dependencies; improved readme. + +**Disclaimer**: false positives; practically this is useful for "exploration" rather than for "enforcement". > **Vertical Ordering** > > In general we want function call dependencies to point in the downward direction. That is, a function that is called should be bellow a function that does the calling. This creates a nice flow down the source code module from the high level to low level. > -> As in newspaper articles, we expect the most important concepts to come first, and we expect them to be expressed with the least amount of polluting detail. We expect the low-level details to come last. This allows us to skim source files, getting the gist from the frist few functions, without having to immerge ourselves in the details. +> As in newspaper articles, we expect the most important concepts to come first, and we expect them to be expressed with the least amount of polluting detail. We expect the low-level details to come last. This allows us to skim source files, getting the gist from the first few functions, without having to immerge ourselves in the details. > -> — Clean Code, Chapter 5, p84, Robert C. Martin, 2009 +> -- Clean Code, Chapter 5, p84, Robert C. Martin, 2009 ## Usage +### Golangci-lint plugin + +- The subpackage `golangci-lint` provides a [module plugin](https://golangci-lint.run/plugins/module-plugins) for golangci-lint. +- Example [.custom-gcl.yml](./golangci-lint/.custom-gcl.yml) and [.golangci.yml](./golangci-lint/.golangci.yml) configuration files are provided as a basis for use in your own project. + +### Go analysis library + +- Use `github.com/ppipada/refdir/analysis/refdir.Analyzer` as per `go/analysis` [docs](<(https://pkg.go.dev/golang.org/x/tools/go/analysis)>) to integrate `refdir` in a custom analysis binary. + ### Standalone ```bash -go install github.com/devnev/refdir@latest +go install github.com/ppipada/refdir@latest refdir ./... ``` -For each reference type (`func`, `type`, `recvtype`, `var`, `const`) there is a flag `--${type}-dir=[up|down|ignore]` to configure the required direction of references of that type. +- For each reference type (`func`, `type`, `recvtype`, `var`, `const`) there is a flag `--${type}-dir=[up|down|ignore]` to configure the required direction of references of that type. + +- Meaning of directions: + + - up: use must be after the declaration (declare above use). + - down: use may be before the declaration (call/use first, define later). + - ignore: skip checks for that kind. + +- Options + + - `--func-dir={down|up|ignore}` + + - What: References to functions and concrete methods (calls, values). + - Note: Interface method selections (i.M) are not func refs; they’re treated as Type refs. + - Default (recommended): down + + - `--type-dir={down|up|ignore}` + + - What: References to named types (in signatures, conversions, literals, etc.) and interface method selections (i.M). + - Excludes: The receiver type in a method declaration (that’s RecvType). + - Default (recommended): up + + - `--recvtype-dir={down|up|ignore}` + + - What: The receiver type name in a method declaration (the T in func (t T) M()). + - Counted once per method; other mentions of T inside that method are ignored. + - Default (recommended): up + + - `--var-dir={down|up|ignore}` + + - What: References to variables. + - Excludes: Struct fields and inner-scope vars. + - Default (recommended): up + + - `--const-dir={down|up|ignore}` -### `go/analysis` + - What: References to constants. + - Excludes: Inner-scope consts. + - Default (recommended): up -Use `github.com/devnev/refdir/analysis/refdir.Analyzer` as per [`go/analysis` docs] to integrate `refdir` in a custom analysis binary. + - `--verbose` -[`go/analysis` docs]: https://pkg.go.dev/golang.org/x/tools/go/analysis + - What: Include informational messages (skips, reasons, positions). + - Default: false -### golangci-lint plugin + - `--color` + - What: Colorize output (OK/info/error). + - Default: true -The subpackage `golangci-lint` provides a [module plugin] for golangci-lint. Example [.custom-gcl.yml] and [.golangci.yml] configuration files are provided as a basis for use in your own project. +## Known limitations -[module plugin]: https://golangci-lint.run/plugins/module-plugins -[.custom-gcl.yml]: ./golangci-lint/.custom-gcl.yml -[.golangci.yml]: ./golangci-lint/.golangci.yml +- Transitive recursion is reported as an issue in either direction. i.e., func A -> func B -> func A. A sample of that is present in this [test](./analysis/refdir/testdata/analysistest/defaultdirs/func_recursive.go). +- Promoted interface methods via embedding: `t.M()` where `M` comes from an embedded interface field won’t be reclassified as a `Type` reference to that interface. +- Type parameters: Calls through type-parameter receivers are skipped. There isn’t a meaningful per-file declaration position to compare against. ## Example -![](./doc/code-dep-viz.png) +![Code graph](code-dep-viz.png) -![](./doc/output-color.png) +![Output example](output-color.png) diff --git a/analysis/refdir/analyzer.go b/analysis/refdir/analyzer.go index 303d818..ee97fdb 100644 --- a/analysis/refdir/analyzer.go +++ b/analysis/refdir/analyzer.go @@ -1,13 +1,14 @@ package refdir import ( + "errors" "flag" "fmt" "go/ast" "go/token" "go/types" - "github.com/devnev/refdir/analysis/refdir/color" + "github.com/ppipada/refdir/analysis/refdir/color" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -16,7 +17,7 @@ import ( var Analyzer = &analysis.Analyzer{ Name: "refdir", - Doc: "Report potential reference-to-decleration ordering issues", + Doc: "Report potential reference-to-declaration ordering issues", Run: run, Flags: flag.FlagSet{}, Requires: []*analysis.Analyzer{inspect.Analyzer}, @@ -61,25 +62,29 @@ var Directions = []Direction{ var RefOrder = map[RefKind]Direction{ Func: Down, - Type: Down, + Type: Up, RecvType: Up, - Var: Down, - Const: Down, + Var: Up, + Const: Up, } func init() { Analyzer.Flags.BoolVar(&verbose, "verbose", false, `print all details`) Analyzer.Flags.BoolVar(&colorize, "color", true, `colorize terminal`) addDirectionFlag := func(kind RefKind, desc string) { - Analyzer.Flags.Func(string(kind)+"-dir", fmt.Sprintf("%s (default %s)", desc, RefOrder[kind]), func(s string) error { - switch dir := Direction(s); dir { - case Down, Up, Ignore: - RefOrder[kind] = dir - return nil - default: - return fmt.Errorf("must be %s, %s, or %s", Up, Down, Ignore) - } - }) + Analyzer.Flags.Func( + string(kind)+"-dir", + fmt.Sprintf("%s (default %s)", desc, RefOrder[kind]), + func(s string) error { + switch dir := Direction(s); dir { + case Down, Up, Ignore: + RefOrder[kind] = dir + return nil + default: + return fmt.Errorf("must be %s, %s, or %s", Up, Down, Ignore) + } + }, + ) } addDirectionFlag(Func, "direction of references to functions and methods") addDirectionFlag(Type, "direction of type references, excluding references to the receiver type") @@ -88,9 +93,7 @@ func init() { addDirectionFlag(Const, "direction of references to const declarations") } -func run(pass *analysis.Pass) (interface{}, error) { - inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - +func run(pass *analysis.Pass) (any, error) { var printer Printer = SimplePrinter{Pass: pass} if colorize { printer = ColorPrinter{ @@ -104,9 +107,14 @@ func run(pass *analysis.Pass) (interface{}, error) { printer = &SortedPrinter{Pass: pass, Printer: printer} defer printer.Flush() + analysisInspector, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, errors.New("could not get analyzer") + } + check := func(ref *ast.Ident, def token.Pos, kind RefKind) { if !def.IsValid() { - // So far only seen on calls to Error method of error interface + // So far only seen on calls to Error method of error interface. printer.Info(ref.Pos(), fmt.Sprintf("got invalid definition position for %q", ref.Name)) return } @@ -117,13 +125,29 @@ func run(pass *analysis.Pass) (interface{}, error) { } if pass.Fset.File(ref.Pos()).Name() != pass.Fset.File(def).Name() { - printer.Info(ref.Pos(), fmt.Sprintf(`%s reference %s is to definition in separate file (%s)`, kind, ref.Name, pass.Fset.Position(def))) + printer.Info( + ref.Pos(), + fmt.Sprintf( + `%s reference %s is to definition in separate file (%s)`, + kind, + ref.Name, + pass.Fset.Position(def), + ), + ) return } refLine, defLine := pass.Fset.Position(ref.Pos()).Line, pass.Fset.Position(def).Line if refLine == defLine { - printer.Ok(ref.Pos(), fmt.Sprintf(`%s reference %s is on same line as definition (%s)`, kind, ref.Name, pass.Fset.Position(def))) + printer.Ok( + ref.Pos(), + fmt.Sprintf( + `%s reference %s is on same line as definition (%s)`, + kind, + ref.Name, + pass.Fset.Position(def), + ), + ) return } @@ -134,7 +158,13 @@ func run(pass *analysis.Pass) (interface{}, error) { } var message string if verbose { - message = fmt.Sprintf(`%s reference %s is %s definition (%s)`, kind, ref.Name, order, pass.Fset.Position(def)) + message = fmt.Sprintf( + `%s reference %s is %s definition (%s)`, + kind, + ref.Name, + order, + pass.Fset.Position(def), + ) } else { message = fmt.Sprintf(`%s reference %s is %s definition`, kind, ref.Name, order) } @@ -146,6 +176,10 @@ func run(pass *analysis.Pass) (interface{}, error) { } } + // Map selector identifiers (the "Sel" in x.Sel) to their selections so we can + // distinguish interface method selections from concrete ones. + selOfIdent := make(map[*ast.Ident]*types.Selection) + // State for keeping track of the receiver type. // No need for a stack as method declarations can only be at file scope. var ( @@ -154,7 +188,7 @@ func run(pass *analysis.Pass) (interface{}, error) { beforeFuncType bool ) - inspect.Nodes(nil, func(n ast.Node, push bool) (proceed bool) { + analysisInspector.Nodes(nil, func(n ast.Node, push bool) (proceed bool) { if !push { if funcDecl == n { funcDecl = nil @@ -170,6 +204,11 @@ func run(pass *analysis.Pass) (interface{}, error) { return false } + case *ast.SelectorExpr: + if sel := pass.TypesInfo.Selections[node]; sel != nil { + selOfIdent[node.Sel] = sel + } + case *ast.FuncDecl: if funcDecl == nil { funcDecl = node @@ -180,17 +219,26 @@ func run(pass *analysis.Pass) (interface{}, error) { beforeFuncType = false case *ast.Ident: - switch def := pass.TypesInfo.Uses[node].(type) { + // If this ident is a definition or otherwise has no associated use, + // skip it to avoid noisy "unexpected ident" messages. + obj := pass.TypesInfo.Uses[node] + if obj == nil { + break + } + + switch def := obj.(type) { case *types.Var: def = def.Origin() - if def.IsField() { - printer.Info(node.Pos(), fmt.Sprintf("skipping var ident %s for field %s", node.Name, pass.Fset.Position(def.Pos()))) - } else if def.Parent() != def.Pkg().Scope() { - printer.Info(node.Pos(), fmt.Sprintf("skipping var ident %s with inner parent scope %s", node.Name, pass.Fset.Position(def.Parent().Pos()))) - } else { + switch { + case def.IsField(): + printer.Info(node.Pos(), fmt.Sprintf("skipping var ident %s for field %s", node.Name, + pass.Fset.Position(def.Pos()))) + case def.Parent() != def.Pkg().Scope(): + printer.Info(node.Pos(), fmt.Sprintf("skipping var ident %s with inner parent scope %s", node.Name, + pass.Fset.Position(def.Parent().Pos()))) + default: check(node, def.Pos(), Var) } - case *types.Const: if def.Parent() != def.Pkg().Scope() { printer.Info(node.Pos(), fmt.Sprintf("skipping var ident %s with inner parent scope %s", node.Name, pass.Fset.Position(def.Parent().Pos()))) @@ -205,7 +253,43 @@ func run(pass *analysis.Pass) (interface{}, error) { curr, ok := pass.TypesInfo.Defs[funcDecl.Name].(*types.Func) if ok && curr != nil && curr.Origin() == def { // For a recursive call, pass.TypesInfo.Uses[node] returns the current function’s object; - // comparing its Origin() to the current func’s Origin() lets us detect direct recursion even with generics instantiation. + // comparing its Origin() to the current func’s Origin() lets us detect direct recursion even + // with generics instantiation. + break + } + } + + // Handle interface method selections as type references. + // If this is a method selection, and the receiver is an interface type, + // treat it as a reference to the interface type (not a function). + if sel := selOfIdent[node]; sel != nil { + recv := sel.Recv() + // Unwrap pointers. + for { + if p, ok := recv.(*types.Pointer); ok { + recv = p.Elem() + continue + } + break + } + handled := false + switch rt := recv.(type) { + case *types.Named: + if _, ok := rt.Underlying().(*types.Interface); ok { + // Count this as a type reference to the named interface. + check(node, rt.Obj().Pos(), Type) + handled = true + } + case *types.Interface: + // Unnamed interface type; nothing to order against at package scope. + printer.Info(node.Pos(), fmt.Sprintf("skipping interface method reference %s on unnamed interface type", node.Name)) + handled = true + case *types.TypeParam: + // Method selected via a type parameter's interface constraint. + printer.Info(node.Pos(), fmt.Sprintf("skipping method reference %s on type parameter %s", node.Name, rt.Obj().Name())) + handled = true + } + if handled { break } } @@ -217,6 +301,10 @@ func run(pass *analysis.Pass) (interface{}, error) { } case *types.TypeName: + if def.Pkg() == nil { + printer.Info(node.Pos(), "skipping predeclared type "+node.Name) + break + } if def.Parent() != def.Pkg().Scope() { printer.Info(node.Pos(), fmt.Sprintf("skipping type ident %s with inner parent scope %s", node.Name, pass.Fset.Position(def.Parent().Pos()))) break @@ -228,11 +316,19 @@ func run(pass *analysis.Pass) (interface{}, error) { break } if funcDecl != nil && recvType == def { - // Reference to the receiver type within a method type or body + // Reference to the receiver type within a method type or body. break } check(node, def.Pos(), Type) + case *types.Builtin: + // Built-in functions like len, make, panic, etc. + printer.Info(node.Pos(), "skipping builtin "+node.Name) + case *types.PkgName: + // Package qualifier in selectors like fmt.Println. + printer.Info(node.Pos(), "skipping package name "+node.Name) + case *types.Label: + printer.Info(node.Pos(), "skipping label "+node.Name) default: printer.Info(node.Pos(), fmt.Sprintf("unexpected ident def type %T for %q", pass.TypesInfo.Uses[node], node.Name)) } @@ -241,5 +337,6 @@ func run(pass *analysis.Pass) (interface{}, error) { return true }) + //nolint:nilnil // Done. return nil, nil } diff --git a/analysis/refdir/analyzer_test.go b/analysis/refdir/analyzer_test.go index 8bb0109..d57e635 100644 --- a/analysis/refdir/analyzer_test.go +++ b/analysis/refdir/analyzer_test.go @@ -15,7 +15,7 @@ func TestAnalyzer_DefaultDirs(t *testing.T) { if err != nil { t.Fatalf("Failted to get workdir: %v", err) } - analysistest.Run(t, filepath.Join(wd, "testdata/analysistest"), Analyzer, "./defaultdirs/...") + analysistest.Run(t, filepath.Join(wd, "testdata", "analysistest"), Analyzer, "./defaultdirs/...") } func TestDefaultRefOrderIsValid(t *testing.T) { diff --git a/analysis/refdir/internal/example_bad/simple.go b/analysis/refdir/internal/example_bad/simple.go index 057424a..59d1277 100644 --- a/analysis/refdir/internal/example_bad/simple.go +++ b/analysis/refdir/internal/example_bad/simple.go @@ -1,3 +1,4 @@ +//nolint:gocritic // Skip gocritic for this. package example import "log" diff --git a/analysis/refdir/internal/example_default_good/simple.go b/analysis/refdir/internal/example_default_good/simple.go index 3f802c9..c61c28b 100644 --- a/analysis/refdir/internal/example_default_good/simple.go +++ b/analysis/refdir/internal/example_default_good/simple.go @@ -34,12 +34,12 @@ func buyFlour() { log.Println("bought flour") } -func FindOven() *Oven { return &Oven{} } - type Oven struct { temperature float32 } +func FindOven() *Oven { return &Oven{} } + func (s *Oven) WarmUp() *Oven { s.temperature += 42 return s diff --git a/analysis/refdir/internal/example_funcup_good/simple.go b/analysis/refdir/internal/example_funcup_good/simple.go index 2d90e86..9602209 100644 --- a/analysis/refdir/internal/example_funcup_good/simple.go +++ b/analysis/refdir/internal/example_funcup_good/simple.go @@ -2,12 +2,12 @@ package example import "log" -func FindOven() *Oven { return &Oven{} } - type Oven struct { temperature float32 } +func FindOven() *Oven { return &Oven{} } + func (s *Oven) WarmUp() *Oven { s.temperature += 42 return s diff --git a/analysis/refdir/printer.go b/analysis/refdir/printer.go index 4763d41..8b8c48c 100644 --- a/analysis/refdir/printer.go +++ b/analysis/refdir/printer.go @@ -4,7 +4,7 @@ import ( "go/token" "sort" - "github.com/devnev/refdir/analysis/refdir/color" + "github.com/ppipada/refdir/analysis/refdir/color" "golang.org/x/tools/go/analysis" ) @@ -69,7 +69,7 @@ func (c ColorPrinter) Ok(p token.Pos, s string) { func (c ColorPrinter) Flush() {} -// function call at position +// Function call at position. type pcall struct { p token.Pos f func() diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_down.go b/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_down.go index 98c8e94..cc6e06e 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_down.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_down.go @@ -1,7 +1,7 @@ package defaultdirs func TestRefDownToStringConst() { - _ = StringConstAtEnd + _ = StringConstAtEnd // want "const reference StringConstAtEnd is before definition" } const StringConstAtEnd = "end" diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_up.go b/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_up.go index 57ba6f3..c28a4a7 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_up.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/const_ref_up.go @@ -3,5 +3,5 @@ package defaultdirs const StringConstAtStart = "top" func TestRefUpToStringConst() { - _ = StringConstAtStart // want "const reference StringConstAtStart is after definition" + _ = StringConstAtStart } diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_down.go b/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_down.go index dbcae24..42b18a6 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_down.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_down.go @@ -1,6 +1,6 @@ package defaultdirs -func TestRefDownToConstraintType[T TestRefDownConstraintType]() {} +func TestRefDownToConstraintType[T TestRefDownConstraintType]() {} // want "type reference TestRefDownConstraintType is before definition" type TestRefDownConstraintType interface { DummyMethod() diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_up.go b/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_up.go index 7ead085..60f4e7b 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_up.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/constraint_ref_up.go @@ -4,4 +4,4 @@ type TestRefUpConstraintType interface { DummyMethod() } -func TestRefUpToConstraintType[T TestRefUpConstraintType]() {} // want "type reference TestRefUpConstraintType is after definition" +func TestRefUpToConstraintType[T TestRefUpConstraintType]() {} diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_down.go b/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_down.go index 8eb9ac2..dfa17d5 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_down.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_down.go @@ -1,7 +1,7 @@ package defaultdirs func TestTypeRefDown() { - _ = TestTypeRefDownType{} + _ = TestTypeRefDownType{} // want "type reference TestTypeRefDownType is before definition" } type TestTypeRefDownType struct{} diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_up.go b/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_up.go index 079419f..a3cccae 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_up.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/type_ref_up.go @@ -3,5 +3,5 @@ package defaultdirs type TestTypeRefUpType struct{} func TestTypeRefUp() { - _ = TestTypeRefUpType{} // want "type reference TestTypeRefUpType is after definition" + _ = TestTypeRefUpType{} } diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_down.go b/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_down.go index 6027711..a29f611 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_down.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_down.go @@ -1,7 +1,7 @@ package defaultdirs func TestRefDownToStringVar() { - _ = StringVarAtEnd + _ = StringVarAtEnd // want "var reference StringVarAtEnd is before definition" } var StringVarAtEnd string diff --git a/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_up.go b/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_up.go index e277ba1..2eb372c 100644 --- a/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_up.go +++ b/analysis/refdir/testdata/analysistest/defaultdirs/var_ref_up.go @@ -3,5 +3,5 @@ package defaultdirs var StringVarAtStart string func TestRefUpToStringVar() { - _ = StringVarAtStart // want "var reference StringVarAtStart is after definition" + _ = StringVarAtStart } diff --git a/doc/code-dep-viz.png b/code-dep-viz.png similarity index 100% rename from doc/code-dep-viz.png rename to code-dep-viz.png diff --git a/go.mod b/go.mod index e6e682f..aa9ca9a 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ -module github.com/devnev/refdir +module github.com/ppipada/refdir go 1.24.0 require ( - github.com/golangci/plugin-module-register v0.1.1 - golang.org/x/tools v0.29.0 + github.com/golangci/plugin-module-register v0.1.2 + golang.org/x/tools v0.39.0 ) require ( - golang.org/x/mod v0.23.0 // indirect - golang.org/x/sync v0.11.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/sync v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index dd5c412..0f39a6c 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ -github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= -github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= +github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= diff --git a/golangci-lint/.custom-gcl.yml b/golangci-lint/.custom-gcl.yml index a7bb695..1f62b5a 100644 --- a/golangci-lint/.custom-gcl.yml +++ b/golangci-lint/.custom-gcl.yml @@ -1,5 +1,5 @@ -version: v1.63.4 +version: v2.6.1 plugins: - - module: github.com/devnev/refdir - import: github.com/devnev/refdir/golangci-lint - version: v0.6.0 + - module: github.com/ppipada/refdir + import: github.com/ppipada/refdir/golangci-lint + version: v0.7.0 diff --git a/golangci-lint/.golangci.yml b/golangci-lint/.golangci.yml index 3c3df6c..2d4d49b 100644 --- a/golangci-lint/.golangci.yml +++ b/golangci-lint/.golangci.yml @@ -1,22 +1,24 @@ -# yaml-language-server: $schema=https://json.schemastore.org/golangci-lint.json -linters-settings: - custom: - refdir: - type: module - description: "Report potential reference-to-decleration ordering issues" - original-url: "github.com/devnev/refdir" - # All settings are optional. - # These example settings correspond to the analyzer defaults. - # Possible values are 'up', 'down', and 'ignore' - settings: - directions: - func: down - type: down - recvtype: up - var: down - const: down +version: "2" linters: - disable-all: true + default: none enable: - refdir + settings: + custom: + refdir: + type: module + description: "Report potential reference-to-declaration ordering issues" + original-url: "github.com/ppipada/refdir" + # All settings are optional. + # These example settings correspond to the analyzer defaults. + # Possible values are 'up', 'down', and 'ignore' + settings: + directions: + func: down + type: up + recvtype: up + var: up + const: up + + diff --git a/golangci-lint/plugins.go b/golangci-lint/plugins.go index dc11dee..8f3909d 100644 --- a/golangci-lint/plugins.go +++ b/golangci-lint/plugins.go @@ -4,8 +4,8 @@ import ( "fmt" "slices" - "github.com/devnev/refdir/analysis/refdir" "github.com/golangci/plugin-module-register/register" + "github.com/ppipada/refdir/analysis/refdir" "golang.org/x/tools/go/analysis" ) diff --git a/golangci-lint/plugins_test.go b/golangci-lint/plugins_test.go index 1d0c8dd..a9d6647 100644 --- a/golangci-lint/plugins_test.go +++ b/golangci-lint/plugins_test.go @@ -11,55 +11,60 @@ import ( func TestPlugin(t *testing.T) { d := t.TempDir() - - cmd := exec.Command(`sh`, `-c`, `golangci-lint custom && mv custom-gcl "${D}"`) + ctx := t.Context() + cmd := exec.CommandContext(ctx, `sh`, `-c`, `golangci-lint custom -v && mv custom-gcl "${D}"`) cmd.Dir = "./testdata" cmd.Env = append(os.Environ(), "D="+d) t.Logf("Building custom linter with %q", cmd.Args) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("Failed to build custom linter: %v, output: %s", err, string(out)) + } else { + t.Logf("output:\n %s", out) } binPath := filepath.Join(d, "custom-gcl") t.Logf("Cleaning cache") - if out, err := exec.Command(binPath, "cache", "clean").CombinedOutput(); err != nil { + if out, err := exec.CommandContext(ctx, binPath, "cache", "clean").CombinedOutput(); err != nil { t.Fatalf("Failed to clear lint cache: %v, output: %s", err, string(out)) } - // Should succeed - + // Should succeed. t.Logf("Running pass checks") - - cmd = exec.Command(binPath, "run", "--config=testdata/.golangci-default.yml", "../analysis/refdir/internal/example_default_good") + cmd = exec.CommandContext(ctx, binPath, "run", "--config=testdata/.golangci-default.yml", + "../analysis/refdir/internal/example_default_good") if _, err := cmd.Output(); err != nil { t.Errorf("Custom linter failed on default example with default config: %s", fmtExitError(err)) } - cmd = exec.Command(binPath, "run", "--config=testdata/.golangci-funcup.yml", "../analysis/refdir/internal/example_funcup_good") + cmd = exec.CommandContext(ctx, binPath, "run", "--config=testdata/.golangci-funcup.yml", + "../analysis/refdir/internal/example_funcup_good") if _, err := cmd.Output(); err != nil { t.Errorf("Expected custom linter to fail on funcup example with funcup config, got %s", fmtExitError(err)) } - // Should fail + // Should fail. t.Logf("Running fail checks") - - cmd = exec.Command(binPath, "run", "--config=testdata/.golangci-default.yml", "../analysis/refdir/internal/example_funcup_good") + cmd = exec.CommandContext(ctx, binPath, "run", "--config=testdata/.golangci-default.yml", + "../analysis/refdir/internal/example_funcup_good") if _, err := cmd.Output(); exitCode(err) != 1 { t.Errorf("Expected custom linter to fail on funcup example with default config, got %s", fmtExitError(err)) } - cmd = exec.Command(binPath, "run", "--config=testdata/.golangci-default.yml", "../analysis/refdir/internal/example_bad") + cmd = exec.CommandContext(ctx, binPath, "run", "--config=testdata/.golangci-default.yml", + "../analysis/refdir/internal/example_bad") if _, err := cmd.Output(); exitCode(err) != 1 { t.Errorf("Expected custom linter to fail on bad example with default config, got %s", fmtExitError(err)) } - cmd = exec.Command(binPath, "run", "--config=testdata/.golangci-funcup.yml", "../analysis/refdir/internal/example_default_good") + cmd = exec.CommandContext(ctx, binPath, "run", "--config=testdata/.golangci-funcup.yml", + "../analysis/refdir/internal/example_default_good") if _, err := cmd.Output(); exitCode(err) != 1 { t.Errorf("Custom linter failed on default example with funcup config: %s", fmtExitError(err)) } - cmd = exec.Command(binPath, "run", "--config=testdata/.golangci-funcup.yml", "../analysis/refdir/internal/example_bad") + cmd = exec.CommandContext(ctx, binPath, "run", "--config=testdata/.golangci-funcup.yml", + "../analysis/refdir/internal/example_bad") if _, err := cmd.Output(); exitCode(err) != 1 { t.Errorf("Expected custom linter to fail on bad example with default config, got %s", fmtExitError(err)) } diff --git a/golangci-lint/testdata/.custom-gcl.yml b/golangci-lint/testdata/.custom-gcl.yml index b726afb..b7ec0c4 100644 --- a/golangci-lint/testdata/.custom-gcl.yml +++ b/golangci-lint/testdata/.custom-gcl.yml @@ -1,5 +1,5 @@ -version: v1.63.4 +version: v2.6.1 plugins: - - module: github.com/devnev/refdir - import: github.com/devnev/refdir/golangci-lint + - module: github.com/ppipada/refdir + import: github.com/ppipada/refdir/golangci-lint path: ../.. diff --git a/golangci-lint/testdata/.golangci-default.yml b/golangci-lint/testdata/.golangci-default.yml index d242571..2ba565c 100644 --- a/golangci-lint/testdata/.golangci-default.yml +++ b/golangci-lint/testdata/.golangci-default.yml @@ -1,10 +1,10 @@ -# yaml-language-server: $schema=https://json.schemastore.org/golangci-lint.json -linters-settings: - custom: - refdir: - type: module +version: "2" linters: - disable-all: true + default: none enable: - refdir + settings: + custom: + refdir: + type: module diff --git a/golangci-lint/testdata/.golangci-funcup.yml b/golangci-lint/testdata/.golangci-funcup.yml index 2031171..92f20d2 100644 --- a/golangci-lint/testdata/.golangci-funcup.yml +++ b/golangci-lint/testdata/.golangci-funcup.yml @@ -1,13 +1,20 @@ -# yaml-language-server: $schema=https://json.schemastore.org/golangci-lint.json -linters-settings: - custom: - refdir: - type: module - settings: - directions: - func: up +version: "2" linters: - disable-all: true + default: none enable: - refdir + settings: + custom: + refdir: + type: module + description: "Report potential reference-to-declaration ordering issues" + original-url: "github.com/ppipada/refdir" + # All settings are optional. + # These example settings correspond to the analyzer defaults. + # Possible values are 'up', 'down', and 'ignore' + settings: + directions: + func: up + + diff --git a/main.go b/main.go index 3cc728d..8017c75 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/devnev/refdir/analysis/refdir" + "github.com/ppipada/refdir/analysis/refdir" "golang.org/x/tools/go/analysis/singlechecker" ) diff --git a/doc/output-color.png b/output-color.png similarity index 100% rename from doc/output-color.png rename to output-color.png diff --git a/taskfile.yml b/taskfile.yml new file mode 100644 index 0000000..b32ca45 --- /dev/null +++ b/taskfile.yml @@ -0,0 +1,46 @@ +# https://taskfile.dev + +version: "3" + +vars: + GREETING: Hello, these are Refdir tasks! + +tasks: + default: + cmds: + - echo "{{.GREETING}}" + silent: true + + cloc: + cmds: + - | + cloc --vcs=git --ignored=ignored.txt \ + --force-lang=Mustache,tmpl --force-lang=INI,env --force-lang=INI,dev --force-lang=INI,prod \ + --exclude_ext=yaml,sum,mod,md5 --not-match-f='LICENSE$|\.gitignore$' \ + . + + godepgraph: + cmds: + - godepgraph -s -o github.com/ppipada/refdir,command-line-arguments main.go | dot -Tpng -o godepgraph.png + + lint-godotfix: + cmds: + - golangci-lint run --fix --enable-only=godot + + lint-gosort: + cmds: + # Use a go get version of this repo. + - refdir ./... + + lint: + cmds: + - golangci-lint run ./... -v + + test: + cmds: + - go test ./... -v + + lt: + cmds: + - task: lint + - task: test