-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
x/tools/go: create modanalysis package #44753
Comments
actually, it looks like somebody has shoehorned this into the existing this seems like a hack, but props to the author, should we bless/upstream/document this implementation or is it worth extending the existing interface to better support this use case? EDIT: this implementation looses out on the cc @tenntenn |
I don't think it makes sense to reuse the interfaces in From a brief look at the description of At the moment, we recommend |
Agreed, but I am not terribly familiar with this interface and wanted to imply that I do not care about the shape of the final interface. Also, I list One is "find the The second is the package modanalysis
import "golang.org/x/tools/go/analysis"
type Analyser struct {
Name string
Doc string
Flags flag.FlagSet
Flags flag.FlagSet
Run func(*Pass) (interface{}, error)
RunDespiteErrors bool
Requires []*Analyzer
ResultType reflect.Type
FactTypes []analysis.Fact
}
func (a Analyser) String() string { return a.Name }
type Pass struct {
Analyser *Analyser
File modfile.File
Report func(analysis.Diagnostic)
ResultOf map[*Analyzer]interface{}
Facts func() []analysis.Fact
} I reused generic data structures from |
How do you envision
Correct. The analysis.Pass interface is conceptually a single (package, analyzer) pair. FWIW I have implemented the equivalent of both the I found the main disadvantage of the And I found the big disadvantage of the One kinda hacky option is that the analysis.Pass interface can be extended with module information from the loaded *packages.Package if/when available. Would the info in packages.Module suffice for the checkers you are writing? ( https://pkg.go.dev/golang.org/x/tools@v0.1.0/go/packages#Module ) Thinking out load: this is easy to forward for singlechecker and multichecker. Not sure about unitchecker, which would need to be supported for this to be viable. That makes the information available, but would be repeated in each package within the module, which is a bit wasteful and would need to be deduplicated to not annoy the user. One also needs to have at least 1 package in the module. (maybe not a big deal?) |
Anything calculated by multiple
None of my lints need package and module information, so I think creating a strong separation is possible, which should simplify the implementation. We should just convince ourselves that there will never be a use case before enforcing that invariant. But any way you slice it, you will be left with some duplication.
The spelling linter hits this mark, though it could also be implemented as two.
The above version example makes a case for module linters needing to pass around facts.
Probably, though currently I have been thinking of the two as separate pipelines.
Yeah, that was the main reason I titled the issue as I did. The main reason to consider it at all is to play into the existing ecosystem:
I am not tied to any specific approach; it is just that this use case seems important, and I picked one that seemed the least controversial. Personally, I think that the best option is to make a v2 interface that acts at the module level, but has iteration logic for interacting at the package level too. This would be fundamentally incompatible with with how we think about
Yeah, I thought of this too, and we could have docs saying wrap any module specific code in a
Technically yes, but only because |
I do not understand this example yet. How this would be put into Result, Facts, and eventually turned into a diagnostic? One specific checker would really help me.
unitchecker is usually 1 process per package so there would still be some duplication of work. Deduplication in an outer layer seems more reliable. More thinking out loud. I don't know where one would store module Facts for the equivalent of a unitchecker mode for modfiles. Usually facts are stored on the with the export data along with the package graph, and is cached. Is a natural place to store export data on the module graph?
Fair enough. But it does maintain the (package, analyzer) action graph. This makes integrating into the existing ecosystem quite a bit easier.
Alright so if including the *modfile.File is sufficient, I don't see a reason why the framework would not do that instead. This could be accomplished by adding a field to Analyzer |
Could you give some more detail about the concrete use-cases you have in mind?
I guess a transitive analysis framework could be useful for computing, say, cut-points to remove a requirement on some version, but I'm not sure how those would be expressed as facts — nor what kinds of facts would be useful, other than “minimum versions transitively imposed by this module”. |
no exclude or replaceThese directives are useful in context, but tend to be a bit of an escape hatch and SHOULD NOT be in released modules. Disallow any entries in // suggest line removal
replace golang.org/x/mod v0.4.2 => golang.org/x/mod v0.4.1
// suggest line removal
exclude golang.org/x/mod v0.4.2 no local replaceMuch like the previous example, local replace statements are anything but portable, so disallow them: // suggest line removal
replace golang.org/x/mod v0.4.2 => /path/to/golang.org/x/mod no pseudo versionsWhen a valid tag has been cut for a repository, pseudo versions should not be required: require(
// suggest v0.4.2
golang.org/x/mod v0.4.3-0.20210310185834-19d50cac98aa
//ok
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f
) no pre-release versionsOnly stable releases should be required: // suggest v1.4.0 if released, otherwise last released tag v0.3.5
require github.com/golang/protobuf v1.4.0-rc.4 no require of redacted versions// suggest next smallest tag, say v1.5.3, otherwise @latest
require k8s.io/client-go v1.5.2 required versions must be latest patchConsuming bugfixes is good: // suggest v0.4.2
require golang.org/x/mod v0.4.1 required versions must be @latest (latest minor and patch)Staying up to date is good: // suggest v0.4.2
require golang.org/x/mod v0.3.0 required versions must be @upgrade (latest major)Some repos move fast and we would like to fix breaking changes as quickly as possible: // suggest v0.4.2
require golang.org/x/mod v0.3.0 required versions must be @masterMaybe you want to maintain require (
// suggest v0.4.3-0.20210310185834-19d50cac98aa
golang.org/x/mod v0.4.2
// suggest v0.0.0-20210220032938-85be41e4509f
golang.org/x/exp v0.0.0-20210212053707-62dc52270d37
) no multiple single line elements, only blocksIMO, reads better: // suggest
/* require (
golang.org/x/mod v0.4.2
golang.org/x/exp v0.0.0-20210212053707-62dc52270d37
) */
require golang.org/x/mod v0.4.2
require golang.org/x/exp v0.0.0-20210212053707-62dc52270d37 (redact) clauses need commentsUseful to document why you are pulling a release: // suggest adding comment
redact v1.2.3 spaces between blocked lines with commentsFor a big list, it is hard determine if the comment applies above or below: // suggest
/* require(
// message one
golang.org/x/mod v0.4.3-0.20210310185834-19d50cac98aa
// message two
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f
)*/
require(
// message one
golang.org/x/mod v0.4.3-0.20210310185834-19d50cac98aa
// message two
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f
) clauses must be in some order (e.g. first go, then require, etc.)Old toolchains did not always add go clause, thus it can end up at the end of the file, plus people do not have a consistent ordering for redact v1.2.3
// move above redact clause
go 1.16 catch spelling errors in comments and keywordsBecause unknown keywords are ignored, comments are unstructured, and english is hard, typos may introduce bugs or reading issues: // suggest redact v1.2.3
redatc v1.2.3
// suggest performance
require golang.org/x/mod v0.4.1 // preformance bug introduced comparing the go directive to the local toolchain version// suggest 1.16 when running with go1.16
go 1.15 |
@carnott-snap Thank you for these examples. Those are pretty compelling for supporting linting the modfiles in some form. If we went the |
Anything that works with external versions suggestions can really benefit from |
background
An analysis framework was built out for the go language, to make writing linters (and metalinters easier).
go.mod
files are another custom dsl, where you may want to enforce standards. Thus it seems reasonable to make an analysis package for modules.One of the big potential benefits is an official "get me the main
go.mod
" function, since currently guessing or slurpinggo env -json
are the best bad options. Furthermore, third partygo.mod
linters have already started popping up: gomodguard.alternatives
The exact location is not terribly important, it could also be
golang.org/x/tools/go/analysis/modanalysis
orgolang.org/x/mod/analysis
, butgolang.org/x/tools/go/analysis/modanalysis
seems reasonable.Instead of adding a whole new package and interface, it may be easier to add a
ModPass
struct based on thego.mod
syntax, a newModRun func(*ModRun) (interface{}, error)
struct field, and the plumbing to read in the maingo.mod
. This seems like a choice for someone more familiar with the current package.The text was updated successfully, but these errors were encountered: