Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gofactory linter #4196

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open

Add gofactory linter #4196

wants to merge 19 commits into from

Conversation

maranqz
Copy link

@maranqz maranqz commented Nov 12, 2023

The linter checks that the structures are created by the factory, and not directly.
The checking helps to provide invariants without exclusion and helps avoid creating an invalid object.

related to #2708

Copy link

boring-cyborg bot commented Nov 12, 2023

Hey, thank you for opening your first Pull Request !

@CLAassistant
Copy link

CLAassistant commented Nov 12, 2023

CLA assistant check
All committers have signed the CLA.

@maranqz maranqz changed the title Addgofactory linter Add gofactory linter Nov 12, 2023
@maranqz maranqz changed the title Add gofactory linter Addgofactory linter Nov 12, 2023
@maranqz maranqz changed the title Addgofactory linter Add gofactory linter Nov 12, 2023
@ldez
Copy link
Member

ldez commented Nov 12, 2023

In order for a pull request adding a linter to be reviewed, the linter and the PR must follow some requirements.

  • The CLA must be signed

Pull Request Description

  • It must have a link to the linter repository.
  • It must provide a short description of the linter.

Linter

  • It must not be a duplicate of another linter or a rule of a linter. (the team will help to verify that)
  • It must have a valid license (AGPL is not allowed) and the file must contain the required information by the license, ex: author, year, etc.
  • The linter repository must have a CI and tests.
  • It must use go/analysis.
  • It must have a valid tag, ex: v1.0.0, v0.1.0.
  • It must not contain init().
  • It must not contain panic().
  • It must not contain log.fatal(), os.exit(), or similar.
  • It must not modify the AST.
  • It must not have false positives/negatives. (the team will help to verify that)
  • It must have tests inside golangci-lint.

The Linter Tests Inside Golangci-lint

  • They must have at least one std lib import.
  • They must work with T=<name of the linter test file>.go make test_linters. (the team will help to verify that)

.golangci.reference.yml

  • The linter must be added to the list of available linters (alphabetical case-insensitive order).
    • enable and disable options
  • If the linter has a configuration, the exhaustive configuration of the linter must be added (alphabetical case-insensitive order)
    • The values must be different from the default ones.
    • The default values must be defined in a comment.
    • The option must have a short description.

Others Requirements

  • The files (tests and linter) inside golangci-lint must have the same name as the linter.
  • The .golangci.yml of golangci-lint itself must not be edited and the linter must not be added to this file.
  • The linters must be sorted in the alphabetical order (case-insensitive) in the Manager.GetAllSupportedLinterConfigs(...) and .golangci.reference.yml.
  • The load mode (WithLoadMode(...)):
    • if the linter doesn't use types: goanalysis.LoadModeSyntax
    • goanalysis.LoadModeTypesInfo required WithLoadForGoAnalysis() in the Manager.GetAllSupportedLinterConfigs(...)
  • The version in WithSince(...) must be the next minor version (v1.X.0) of golangci-lint.
  • WithURL() must contain the URL of the repository.

Recommendations

  • The linter should not use SSA. (SSA can be a problem with generics)
  • The linter repository should have a readme and linting.
  • The linter should be published as a binary. (useful to diagnose bug origins)

The golangci-lint team will edit this comment to check the boxes before and during the review.

The code review will start after the completion of those checkboxes (except for the specific items that the team will help to verify).

If the author of the PR is a member of the golangci-lint team, he should not edit this message.

This checklist does not imply that we will accept the linter.

@ldez ldez added the linter: new Support new linter label Nov 12, 2023
go.mod Outdated Show resolved Hide resolved
@Antonboom
Copy link
Contributor

Maybe to combine with check from #3488?

@ldez ldez added the feedback required Requires additional feedback label Nov 14, 2023
@maranqz
Copy link
Author

maranqz commented Nov 15, 2023

Maybe to combine with check from #3488?

I don't want to combine constructor and gofactory because the DDD factory can be a function or separate struct method, not only a constructor.
Also, I think that IssueLoan is more descriptive than NewLoan.

2. Added tests
3. Added config
4. Added linter
@maranqz
Copy link
Author

maranqz commented Nov 15, 2023

Is there anything else that I can do to get the PR be approved and merged?

Copy link
Contributor

@Antonboom Antonboom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the linter!

In addition to review comments:

  1. TestdataDir() -> analysistest.TestData()
  2. Case like
type OtherStruct struct {}

type Struct struct {
	Other OtherStruct 
}

func NewStruct() Struct {
	return Struct{
		Other: OtherStruct{}, // warning?
	}
}

Type assertion, type declaration and type underlying, tests <- Broken link

  1. Struct with type params?
type Struct[T any] struct {  // type params could be many (this is different ast node)
}

_ = Struct[T]{} // Warning
  1. Import alias (and affect on report warning?).
  2. You covered slice, but not map, channel, func call, etc. places where obj could be constructed.

pkg/config/linters_settings.go Outdated Show resolved Hide resolved
pkg/config/linters_settings.go Outdated Show resolved Hide resolved
pkg/config/linters_settings.go Outdated Show resolved Hide resolved
test/testdata/configs/gofactory.yml Outdated Show resolved Hide resolved
test/testdata/gofactory/app.go Outdated Show resolved Hide resolved
go.mod Outdated Show resolved Hide resolved
maranqz added a commit to maranqz/gofactory that referenced this pull request Nov 19, 2023
# golangci/golangci-lint#4196
1. TestdataDir() -> analysistest.TestData()
2. Described False-Negative cases as testcases and in README.md
3. Fixed link for type_nested.go.skip in README.md
4. Added testcase with generic
5. Added testcase with alias
6. Added testcase with map, chan, array, func
# golangci/golangci-lint#4196
7. Added glob syntax for pkgs
8. Renamed options
9. Extended unexpected code message
10. Added unimplemented testcases
@maranqz
Copy link
Author

maranqz commented Nov 19, 2023

Could I resolve the case on Winter Weekends, not now?)
I added unimplemented testcases for the case and also for chan, var declaration, named returned.

  1. Case like
type OtherStruct struct {}

type Struct struct {
	Other OtherStruct 
}

func NewStruct() Struct {
	return Struct{
		Other: OtherStruct{}, // warning?
	}
}

@ldez
Copy link
Member

ldez commented Nov 20, 2023

Just a reminder for the maintainers: before reviewing the code, the checklist should be handled.

@ldez
Copy link
Member

ldez commented Nov 20, 2023

The use of the tests dedicated to linters that need dependencies is not the right way.
The tests should handled as simple linters.

I'm not sure about the pertinence of this linter because Go doesn't require constructors, and data structure is a good pattern, I think this will lead to a lot of false positives.

@maranqz
Copy link
Author

maranqz commented Nov 21, 2023

The use of the tests dedicated to linters that need dependencies is not the right way.
The tests should handled as simple linters.

Do you mean tests should be like this and remove subdirectories?

package gofactory

import "net/http"

func local() {
   _ = http.Request{} // want...
  _, _ = http.NewRequest("GET", "http://example.com", nil)
}

@maranqz
Copy link
Author

maranqz commented Nov 21, 2023

I'm not sure about the pertinence of this linter because Go doesn't require constructors, and data structure is a good pattern, I think this will lead to a lot of false positives.

My motivation was to create the linter for the domain layer with business logic in the factories, which cannot be skipped and where any structures should always be valid.

I agree that using the go-factory-lint in any place is not a good idea, so I added options packageGlobs and onlyPackageGlobs to set domain packages to use the linter only for them.

Should I force set onlyPackageGlobs as true by default and ask to set packageGlobs to run linter?

go.mod Outdated Show resolved Hide resolved
@maranqz
Copy link
Author

maranqz commented Nov 26, 2023

The use of the tests dedicated to linters that need dependencies is not the right way.
The tests should handled as simple linters.

I removed subdirectories in tests. I'm not sure what you mean.
If not, then you mean that I should remove the use of the WithLoadForGoAnasis option.

I can't because the linter needs this to determine whether it is a function or a type when we cast types like this.

package casting

import (
	"factory/unimplemented/casting/nested"
)

func ToNestedMyInt() {
	_ = nested.MyInt(1) // want `Use factory for nested.Struct`
}

@@ -137,7 +137,7 @@ func TestCgoOk(t *testing.T) {
"--timeout=3m",
"--enable-all",
"-D",
"nosnakecase,gci",
"nosnakecase,gci,gofactory",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope that is correct solution to pass tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your tests are not in the right places.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean I should move test/testdata/gofactory/blocked.go to test/testdata/gofactory/gofactory_package_globs_only.go?

Copy link
Member

@ldez ldez Dec 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, your tests must be inside test/testdata/ and not a dedicated folder.
and your linter should be removed from test/linters_test.go.

#4196 (comment)

pkg/lint/lintersdb/manager.go Show resolved Hide resolved
@@ -355,7 +355,6 @@ func TestLineDirectiveProcessedFiles(t *testing.T) {
func TestUnsafeOk(t *testing.T) {
testshared.NewRunnerBuilder(t).
WithNoConfig().
WithArgs("--enable-all").
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this change relate to PR?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I return, can I add -D gofactory to fix linter error Use factory for unsafe.Pointer.
Or is solution from comment is better?
#4196 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be reverted.

@@ -137,7 +137,7 @@ func TestCgoOk(t *testing.T) {
"--timeout=3m",
"--enable-all",
"-D",
"nosnakecase,gci",
"nosnakecase,gci,gofactory",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to rollback

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added because of linter error Use factory for unsafe.Pointer.

Should I add option to exclude checking of std libraries in gofactory?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I add option to exclude checking of std libraries in gofactory?

Nice idea, but I think in case of exclude-std=false we will receive false positive anyway, because a lot of std types could be (or must be) used without factory by design. Like unsafe.Pointer or sync.Mutex.

Zero value is useful 🙂

Look at

$ cd $GOROOT/src
$ gofactory -json ./... | jq '.[] | .[] | .[] .message' | sort | uniq

func call(_ http.Request) {}

func alias() {
_ = alias_blocked.Request{} // want `Use factory for http.Request`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cosmetic question: why not Use factory for alias_blocked.Request?
at fist time I was confused, but cannot decide what option is better 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk too)
Could you decide how to better based on your experience?

I checked wrapcheck, go-reassign. They both used different printing way for package names.
I don't find another linter which uses package names.

wrapcheck: error returned from external package is unwrapped: sig: func encoding/json.Marshal(v any) ([]byte, error)
go-reassign: reassigning variable EOF in other package alias

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, the option that is better for grep and common understanding – to keep the source code and warnings for it consistent:

_ = neturl.URL{}  // want `Use factory for neturl.URL`
_ = &url.URL{}    // want `Use factory for url.URL`

@golangci golangci deleted a comment from anfaas1618 Mar 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feedback required Requires additional feedback linter: new Support new linter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants