modfile: parse deprecation notices in module comments
Deprecation notices start with "Deprecated:" at the beginning of a
line and run until the end of the paragraph.
This CL reuses text extraction code for retraction rationale, so the
same rules apply: comment text may be from the comments above a
"module" directive or as a suffix on the same line.
For golang/go#40357
Change-Id: Id5524149c6bbda3effc64c6b668b701b5cf428af
Reviewed-on: https://go-review.googlesource.com/c/mod/+/301089
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/modfile/rule.go b/modfile/rule.go
index 8843aee..3f603fa 100644
--- a/modfile/rule.go
+++ b/modfile/rule.go
@@ -47,8 +47,9 @@
// A Module is the module statement.
type Module struct {
- Mod module.Version
- Syntax *Line
+ Mod module.Version
+ Deprecated string
+ Syntax *Line
}
// A Go is the go statement.
@@ -278,7 +279,11 @@
errorf("repeated module statement")
return
}
- f.Module = &Module{Syntax: line}
+ deprecated := parseDeprecation(block, line)
+ f.Module = &Module{
+ Syntax: line,
+ Deprecated: deprecated,
+ }
if len(args) != 1 {
errorf("usage: module module/path")
return
@@ -392,7 +397,7 @@
})
case "retract":
- rationale := parseRetractRationale(block, line)
+ rationale := parseDirectiveComment(block, line)
vi, err := parseVersionInterval(verb, "", &args, dontFixRetract)
if err != nil {
if strict {
@@ -619,10 +624,29 @@
return t, nil
}
-// parseRetractRationale extracts the rationale for a retract directive from the
-// surrounding comments. If the line does not have comments and is part of a
-// block that does have comments, the block's comments are used.
-func parseRetractRationale(block *LineBlock, line *Line) string {
+var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`)
+
+// parseDeprecation extracts the text of comments on a "module" directive and
+// extracts a deprecation message from that.
+//
+// A deprecation message is contained in a paragraph within a block of comments
+// that starts with "Deprecated:" (case sensitive). The message runs until the
+// end of the paragraph and does not include the "Deprecated:" prefix. If the
+// comment block has multiple paragraphs that start with "Deprecated:",
+// parseDeprecation returns the message from the first.
+func parseDeprecation(block *LineBlock, line *Line) string {
+ text := parseDirectiveComment(block, line)
+ m := deprecatedRE.FindStringSubmatch(text)
+ if m == nil {
+ return ""
+ }
+ return m[1]
+}
+
+// parseDirectiveComment extracts the text of comments on a directive.
+// If the directive's line does not have comments and is part of a block that
+// does have comments, the block's comments are used.
+func parseDirectiveComment(block *LineBlock, line *Line) string {
comments := line.Comment()
if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 {
comments = block.Comment()
diff --git a/modfile/rule_test.go b/modfile/rule_test.go
index 96ef036..d721c71 100644
--- a/modfile/rule_test.go
+++ b/modfile/rule_test.go
@@ -499,6 +499,118 @@
},
}
+var moduleDeprecatedTests = []struct {
+ desc, in, want string
+}{
+ // retractRationaleTests exercises some of the same code, so these tests
+ // don't exhaustively cover comment extraction.
+ {
+ `no_comment`,
+ `module m`,
+ ``,
+ },
+ {
+ `other_comment`,
+ `// yo
+ module m`,
+ ``,
+ },
+ {
+ `deprecated_no_colon`,
+ `//Deprecated
+ module m`,
+ ``,
+ },
+ {
+ `deprecated_no_space`,
+ `//Deprecated:blah
+ module m`,
+ `blah`,
+ },
+ {
+ `deprecated_simple`,
+ `// Deprecated: blah
+ module m`,
+ `blah`,
+ },
+ {
+ `deprecated_lowercase`,
+ `// deprecated: blah
+ module m`,
+ ``,
+ },
+ {
+ `deprecated_multiline`,
+ `// Deprecated: one
+ // two
+ module m`,
+ "one\ntwo",
+ },
+ {
+ `deprecated_mixed`,
+ `// some other comment
+ // Deprecated: blah
+ module m`,
+ ``,
+ },
+ {
+ `deprecated_middle`,
+ `// module m is Deprecated: blah
+ module m`,
+ ``,
+ },
+ {
+ `deprecated_multiple`,
+ `// Deprecated: a
+ // Deprecated: b
+ module m`,
+ "a\nDeprecated: b",
+ },
+ {
+ `deprecated_paragraph`,
+ `// Deprecated: a
+ // b
+ //
+ // c
+ module m`,
+ "a\nb",
+ },
+ {
+ `deprecated_paragraph_space`,
+ `// Deprecated: the next line has a space
+ //
+ // c
+ module m`,
+ "the next line has a space",
+ },
+ {
+ `deprecated_suffix`,
+ `module m // Deprecated: blah`,
+ `blah`,
+ },
+ {
+ `deprecated_mixed_suffix`,
+ `// some other comment
+ module m // Deprecated: blah`,
+ ``,
+ },
+ {
+ `deprecated_mixed_suffix_paragraph`,
+ `// some other comment
+ //
+ module m // Deprecated: blah`,
+ `blah`,
+ },
+ {
+ `deprecated_block`,
+ `// Deprecated: blah
+ module (
+ m
+ )`,
+ `blah`,
+ },
+}
+
var sortBlocksTests = []struct {
desc, in, out string
strict bool
@@ -848,6 +960,20 @@
}
}
+func TestModuleDeprecated(t *testing.T) {
+ for _, tt := range moduleDeprecatedTests {
+ t.Run(tt.desc, func(t *testing.T) {
+ f, err := Parse("in", []byte(tt.in), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if f.Module.Deprecated != tt.want {
+ t.Errorf("got %q; want %q", f.Module.Deprecated, tt.want)
+ }
+ })
+ }
+}
+
func TestSortBlocks(t *testing.T) {
for _, tt := range sortBlocksTests {
t.Run(tt.desc, func(t *testing.T) {