@@ -19,6 +19,7 @@ import (
19
19
"errors"
20
20
"fmt"
21
21
"os"
22
+ "path/filepath"
22
23
"strings"
23
24
"sync"
24
25
@@ -79,7 +80,7 @@ func newCli(args []string) *cli {
79
80
fs .StringArrayVar (& protoDescFlag , "descriptor-set-in" , nil , "The file containing a FileDescriptorSet for searching proto imports.\n May be specified multiple times." )
80
81
fs .StringArrayVar (& ruleEnableFlag , "enable-rule" , nil , "Enable a rule with the given name.\n May be specified multiple times." )
81
82
fs .StringArrayVar (& ruleDisableFlag , "disable-rule" , nil , "Disable a rule with the given name.\n May be specified multiple times." )
82
- fs .BoolVar (& listRulesFlag , "list-rules" , false , "Print the rules and exit. Honors the output-format flag." )
83
+ fs .BoolVar (& listRulesFlag , "list-rules" , false , "Print the rules and exit. Honors the output-format flag." )
83
84
fs .BoolVar (& debugFlag , "debug" , false , "Run in debug mode. Panics will print stack." )
84
85
fs .BoolVar (& ignoreCommentDisablesFlag , "ignore-comment-disables" , false , "If set to true, disable comments will be ignored.\n This is helpful when strict enforcement of AIPs are necessary and\n proto definitions should not be able to disable checks." )
85
86
@@ -150,9 +151,12 @@ func (c *cli) lint(rules lint.RuleRegistry, configs lint.Configs) error {
150
151
}
151
152
var errorsWithPos []protoparse.ErrorWithPos
152
153
var lock sync.Mutex
154
+
155
+ imports := resolveImports (c .ProtoImportPaths )
156
+
153
157
// Parse proto files into `protoreflect` file descriptors.
154
158
p := protoparse.Parser {
155
- ImportPaths : append ( c . ProtoImportPaths , "." ) ,
159
+ ImportPaths : imports ,
156
160
IncludeSourceCodeInfo : true ,
157
161
LookupImport : lookupImport ,
158
162
ErrorReporter : func (errorWithPos protoparse.ErrorWithPos ) error {
@@ -165,21 +169,7 @@ func (c *cli) lint(rules lint.RuleRegistry, configs lint.Configs) error {
165
169
},
166
170
}
167
171
// Resolve file absolute paths to relative ones.
168
- // Using supplied import paths first.
169
- protoFiles := c .ProtoFiles
170
- if len (c .ProtoImportPaths ) > 0 {
171
- protoFiles , err = protoparse .ResolveFilenames (c .ProtoImportPaths , c .ProtoFiles ... )
172
- if err != nil {
173
- return err
174
- }
175
- }
176
- // Then resolve again against ".", the local directory.
177
- // This is necessary because ResolveFilenames won't resolve a path if it
178
- // relative to *at least one* of the given import paths, which can result
179
- // in duplicate file parsing and compilation errors, as seen in #1465 and
180
- // #1471. So we resolve against local (default) and flag specified import
181
- // paths separately.
182
- protoFiles , err = protoparse .ResolveFilenames ([]string {"." }, protoFiles ... )
172
+ protoFiles , err := protoparse .ResolveFilenames (p .ImportPaths , c .ProtoFiles ... )
183
173
if err != nil {
184
174
return err
185
175
}
@@ -305,3 +295,75 @@ func getOutputFormatFunc(formatType string) formatFunc {
305
295
}
306
296
return yaml .Marshal
307
297
}
298
+
299
+ func resolveImports (imports []string ) []string {
300
+ // If no import paths are provided, default to the current directory.
301
+ if len (imports ) == 0 {
302
+ return []string {"." }
303
+ }
304
+
305
+ // Get the absolute path of the current working directory.
306
+ cwd , err := os .Getwd ()
307
+ if err != nil {
308
+ // Fallback: If we can't get CWD, return only the provided paths and "."
309
+ seen := map [string ]bool {
310
+ "." : true ,
311
+ }
312
+ result := []string {"." } // Always include "."
313
+ for _ , p := range imports {
314
+ if ! seen [p ] {
315
+ seen [p ] = true
316
+ result = append (result , p )
317
+ }
318
+ }
319
+ return result
320
+ }
321
+
322
+ // Resolve the canonical path for the current working directory.
323
+ // This helps with symlinks (e.g., /var vs /private/var on macOS).
324
+ evaluatedCwd , err := filepath .EvalSymlinks (cwd )
325
+ if err != nil {
326
+ // Fallback to Clean if EvalSymlinks fails (e.g., path does not exist)
327
+ evaluatedCwd = filepath .Clean (cwd )
328
+ }
329
+
330
+ // Initialize resolvedImports with "." and track its canonical absolute path.
331
+ resolvedImports := []string {"." }
332
+ seenAbsolutePaths := map [string ]bool {
333
+ evaluatedCwd : true , // Mark canonical CWD as seen
334
+ }
335
+
336
+ for _ , p := range imports {
337
+ absPath , err := filepath .Abs (p )
338
+ if err != nil {
339
+ // If we can't get the absolute path, treat it as an external path
340
+ // and add it if not already seen (by its original string form).
341
+ if ! seenAbsolutePaths [p ] {
342
+ seenAbsolutePaths [p ] = true
343
+ resolvedImports = append (resolvedImports , p )
344
+ }
345
+ continue
346
+ }
347
+
348
+ // Resolve the canonical path for the current import path.
349
+ evaluatedAbsPath , err := filepath .EvalSymlinks (absPath )
350
+ if err != nil {
351
+ // Fallback to Clean if EvalSymlinks fails
352
+ evaluatedAbsPath = filepath .Clean (absPath )
353
+ }
354
+
355
+ // Check if the current import path's canonical form is the CWD's canonical form
356
+ // or a subdirectory of it. If so, it's covered by ".", so we skip it.
357
+ if evaluatedAbsPath == evaluatedCwd || strings .HasPrefix (evaluatedAbsPath , evaluatedCwd + string (os .PathSeparator )) {
358
+ continue
359
+ }
360
+
361
+ // Add the original path if its canonical absolute form has not been seen before.
362
+ if ! seenAbsolutePaths [evaluatedAbsPath ] {
363
+ seenAbsolutePaths [evaluatedAbsPath ] = true
364
+ resolvedImports = append (resolvedImports , p )
365
+ }
366
+ }
367
+
368
+ return resolvedImports
369
+ }
0 commit comments