rubydex 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -7
  3. data/THIRD_PARTY_LICENSES.html +238 -2
  4. data/exe/rdx +2 -149
  5. data/ext/rubydex/config.c +140 -0
  6. data/ext/rubydex/config.h +16 -0
  7. data/ext/rubydex/definition.c +34 -0
  8. data/ext/rubydex/definition.h +3 -0
  9. data/ext/rubydex/diagnostic.c +28 -1
  10. data/ext/rubydex/diagnostic.h +2 -0
  11. data/ext/rubydex/extconf.rb +4 -2
  12. data/ext/rubydex/graph.c +71 -55
  13. data/ext/rubydex/graph.h +6 -0
  14. data/ext/rubydex/query.c +398 -16
  15. data/ext/rubydex/rubydex.c +2 -0
  16. data/ext/rubydex/utils.c +11 -4
  17. data/lib/ruby_lsp/rubydex/addon.rb +211 -0
  18. data/lib/rubydex/cli/command/console.rb +55 -0
  19. data/lib/rubydex/cli/command/lint/explain.rb +76 -0
  20. data/lib/rubydex/cli/command/lint.rb +208 -0
  21. data/lib/rubydex/cli/command/mcp.rb +30 -0
  22. data/lib/rubydex/cli/command/query.rb +70 -0
  23. data/lib/rubydex/cli/command/skill.rb +69 -0
  24. data/lib/rubydex/cli/command.rb +168 -0
  25. data/lib/rubydex/cli.rb +93 -0
  26. data/lib/rubydex/config.rb +59 -0
  27. data/lib/rubydex/diagnostic.rb +12 -3
  28. data/lib/rubydex/errors.rb +42 -1
  29. data/lib/rubydex/graph.rb +10 -3
  30. data/lib/rubydex/linter/custom_rule.rb +97 -0
  31. data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
  32. data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
  33. data/lib/rubydex/linter/rule_loader.rb +36 -0
  34. data/lib/rubydex/linter/rule_test_case.rb +343 -0
  35. data/lib/rubydex/linter/runner.rb +56 -0
  36. data/lib/rubydex/linter.rb +19 -0
  37. data/lib/rubydex/location.rb +3 -0
  38. data/lib/rubydex/mcp_server.rb +1 -2
  39. data/lib/rubydex/related_information.rb +17 -0
  40. data/lib/rubydex/rule.rb +33 -0
  41. data/lib/rubydex/rules/dynamic_ancestor.rb +29 -0
  42. data/lib/rubydex/rules/dynamic_constant_reference.rb +25 -0
  43. data/lib/rubydex/rules/dynamic_singleton_definition.rb +30 -0
  44. data/lib/rubydex/rules/invalid_constant_visibility.rb +28 -0
  45. data/lib/rubydex/rules/invalid_method_visibility.rb +28 -0
  46. data/lib/rubydex/rules/parse_error.rb +25 -0
  47. data/lib/rubydex/rules/parse_warning.rb +28 -0
  48. data/lib/rubydex/rules/top_level_mixin_self.rb +27 -0
  49. data/lib/rubydex/rules/undefined_constant_visibility_target.rb +27 -0
  50. data/lib/rubydex/rules/undefined_method_visibility_target.rb +27 -0
  51. data/lib/rubydex/severity.rb +70 -0
  52. data/lib/rubydex/skill.rb +89 -0
  53. data/lib/rubydex/skill_registry.rb +62 -0
  54. data/lib/rubydex/version.rb +1 -1
  55. data/lib/rubydex.rb +8 -0
  56. data/lib/rubydex_linter/rules/rule_structure.rb +140 -0
  57. data/rbi/rubydex.rbi +464 -21
  58. data/rust/Cargo.lock +2 -2
  59. data/rust/rubydex/Cargo.toml +5 -1
  60. data/rust/rubydex/benches/graph_memory.rs +3 -5
  61. data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
  62. data/rust/rubydex/src/config.rs +538 -157
  63. data/rust/rubydex/src/diagnostic.rs +162 -45
  64. data/rust/rubydex/src/errors.rs +0 -1
  65. data/rust/rubydex/src/indexing/local_graph.rs +6 -5
  66. data/rust/rubydex/src/indexing/rbs_indexer.rs +270 -6
  67. data/rust/rubydex/src/indexing/ruby_indexer.rs +10 -12
  68. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +134 -81
  69. data/rust/rubydex/src/lib.rs +1 -0
  70. data/rust/rubydex/src/listing.rs +26 -1
  71. data/rust/rubydex/src/main.rs +7 -4
  72. data/rust/rubydex/src/model/declaration.rs +302 -219
  73. data/rust/rubydex/src/model/graph.rs +27 -40
  74. data/rust/rubydex/src/model/name.rs +58 -17
  75. data/rust/rubydex/src/operation/ruby_builder.rs +30 -45
  76. data/rust/rubydex/src/path_helpers.rs +77 -0
  77. data/rust/rubydex/src/query/cypher/schema.rs +63 -0
  78. data/rust/rubydex/src/query/cypher/tests.rs +26 -1
  79. data/rust/rubydex/src/query/cypher.rs +8 -11
  80. data/rust/rubydex/src/query.rs +314 -44
  81. data/rust/rubydex/src/resolution.rs +139 -187
  82. data/rust/rubydex/src/resolution_tests.rs +247 -19
  83. data/rust/rubydex/src/test_utils/context.rs +2 -1
  84. data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
  85. data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
  86. data/rust/rubydex/tests/cli.rs +4 -4
  87. data/rust/rubydex-sys/src/config_api.rs +205 -0
  88. data/rust/rubydex-sys/src/cypher_api.rs +791 -0
  89. data/rust/rubydex-sys/src/definition_api.rs +68 -1
  90. data/rust/rubydex-sys/src/diagnostic_api.rs +42 -8
  91. data/rust/rubydex-sys/src/graph_api.rs +125 -205
  92. data/rust/rubydex-sys/src/lib.rs +2 -0
  93. data/rust/rubydex-sys/src/name_api.rs +49 -17
  94. data/rust/rubydex-sys/src/utils.rs +37 -0
  95. data/skills/send-private-method/SKILL.md +133 -0
  96. metadata +42 -2
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
5
+
6
+ module Rubydex
7
+ module Rules
8
+ # Method visibility operations that depend on dynamic values cannot be reasoned about statically because they
9
+ # depend on runtime values. The program may still be valid, but the quality of the analysis degrades.
10
+ #
11
+ # ```ruby
12
+ # var.private :foo
13
+ # ^^^ Invalid method visibility. This might be correct, but it cannot be understood by the analysis.
14
+ #
15
+ # private(var)
16
+ # ^^^ Invalid method visibility.
17
+ # ```
18
+ class InvalidMethodVisibility < Rule
19
+ class << self
20
+ # @override
21
+ #: -> singleton(Severity::Base)
22
+ def default_severity
23
+ Severity::Warning
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
5
+
6
+ module Rubydex
7
+ module Rules
8
+ # A parse error represents invalid Ruby syntax and a program that will fail to execute. For example, a missing
9
+ # `end`, an unterminated string, a missing parenthesis.
10
+ #
11
+ # ```ruby
12
+ # class Foo
13
+ # ^^^^^ Syntax error. Missing end token
14
+ # ```
15
+ class ParseError < Rule
16
+ class << self
17
+ # @override
18
+ #: -> singleton(Severity::Base)
19
+ def default_severity
20
+ Severity::Error
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
5
+
6
+ module Rubydex
7
+ module Rules
8
+ # Parse warnings represent code that has valid syntax, but may not do what the developer expects. For example,
9
+ # local variables that are completely unused or usage in a void context (creating a line of code that does
10
+ # nothing).
11
+ #
12
+ # ```ruby
13
+ # CONST = 1
14
+ # CONST
15
+ # ^^^^^ Constant used in void context (the expression does nothing).
16
+ # puts CONST + 2
17
+ # ```
18
+ class ParseWarning < Rule
19
+ class << self
20
+ # @override
21
+ #: -> singleton(Severity::Base)
22
+ def default_severity
23
+ Severity::Warning
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
5
+
6
+ module Rubydex
7
+ module Rules
8
+ # The top level of a Ruby program is the special <main> object. It is not possible to use `include self` or
9
+ # `extend self` on that object.
10
+ #
11
+ # ```ruby
12
+ # include self
13
+ # ^^^^ Cannot include self at the top level
14
+ # extend self
15
+ # ^^^^ Cannot extend self at the top level
16
+ # ```
17
+ class TopLevelMixinSelf < Rule
18
+ class << self
19
+ # @override
20
+ #: -> singleton(Severity::Base)
21
+ def default_severity
22
+ Severity::Information
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
5
+
6
+ module Rubydex
7
+ module Rules
8
+ # Undefined constant visibility target means the analysis couldn't find the definition for the constant the code is
9
+ # attempting to change visibility of. It could be defined through meta-programming or it indeed does not exist.
10
+ #
11
+ # ```ruby
12
+ # class Foo
13
+ # private_constant :Bar
14
+ # ^^^ Undefined constant visibility target. The constant `Bar` is not defined.
15
+ # end
16
+ # ```
17
+ class UndefinedConstantVisibilityTarget < Rule
18
+ class << self
19
+ # @override
20
+ #: -> singleton(Severity::Base)
21
+ def default_severity
22
+ Severity::Warning
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
5
+
6
+ module Rubydex
7
+ module Rules
8
+ # Undefined method visibility target means the analysis couldn't find the definition for the method the code is
9
+ # attempting to change visibility of. It could be defined through meta-programming or it indeed does not exist.
10
+ #
11
+ # ```ruby
12
+ # class Foo
13
+ # private :bar
14
+ # ^^^ Undefined method visibility target. The method `bar` is not defined.
15
+ # end
16
+ # ```
17
+ class UndefinedMethodVisibilityTarget < Rule
18
+ class << self
19
+ # @override
20
+ #: -> singleton(Severity::Base)
21
+ def default_severity
22
+ Severity::Warning
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubydex
4
+ module Severity
5
+ # @abstract
6
+ class Base
7
+ class << self
8
+ # @abstract
9
+ #: () -> Symbol
10
+ def value
11
+ raise NotImplementedError, "Subclasses must implement the value method"
12
+ end
13
+ end
14
+ end
15
+
16
+ class Error < Base
17
+ class << self
18
+ # @override
19
+ #: () -> Symbol
20
+ def value
21
+ :error
22
+ end
23
+ end
24
+ end
25
+
26
+ class Warning < Base
27
+ class << self
28
+ # @override
29
+ #: () -> Symbol
30
+ def value
31
+ :warning
32
+ end
33
+ end
34
+ end
35
+
36
+ class Information < Base
37
+ class << self
38
+ # @override
39
+ #: () -> Symbol
40
+ def value
41
+ :information
42
+ end
43
+ end
44
+ end
45
+
46
+ class Hint < Base
47
+ class << self
48
+ # @override
49
+ #: () -> Symbol
50
+ def value
51
+ :hint
52
+ end
53
+ end
54
+ end
55
+
56
+ # All severities order based on their importance.
57
+ ALL = [Error, Warning, Information, Hint].freeze #: Array[singleton(Base)]
58
+ VALUE_MAP = ALL.to_h { |severity| [severity.value, severity] }.freeze #: Hash[Symbol, singleton(Base)]
59
+
60
+ class << self
61
+ #: (Symbol) -> singleton(Base)
62
+ def from_value(value)
63
+ severity = VALUE_MAP[value]
64
+ return severity if severity
65
+
66
+ raise ArgumentError, "Invalid severity #{value.inspect}. Expected one of: #{VALUE_MAP.keys.join(", ")}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "rubydex/errors"
5
+
6
+ module Rubydex
7
+ # A skill file loaded from disk. Skill files are Markdown documents with YAML frontmatter
8
+ # that declare a `name` and `description`. The body is the Markdown content after the frontmatter.
9
+ class Skill
10
+ #: String
11
+ attr_reader :name
12
+
13
+ #: String
14
+ attr_reader :description
15
+
16
+ #: String
17
+ attr_reader :body
18
+
19
+ class << self
20
+ #: (String path) -> Rubydex::Skill
21
+ def load(path)
22
+ from_content(File.read(path), path:)
23
+ rescue SystemCallError, IOError => e
24
+ raise SkillError, "Cannot read skill file #{path}: #{e.message}"
25
+ end
26
+
27
+ #: (String content, ?path: String?) -> Rubydex::Skill
28
+ def from_content(content, path: nil)
29
+ new(**parse(content, path:))
30
+ end
31
+
32
+ private
33
+
34
+ #: (String content, path: String?) -> { name: String, description: String, body: String }
35
+ def parse(content, path:)
36
+ lines = content.lines
37
+
38
+ unless lines.first&.strip == "---"
39
+ raise SkillError, "Missing YAML frontmatter delimiter#{location(path)}"
40
+ end
41
+
42
+ closing_index = (1...lines.length).find { |i| lines[i].strip == "---" }
43
+ unless closing_index
44
+ raise SkillError, "Unterminated YAML frontmatter#{location(path)}"
45
+ end
46
+
47
+ frontmatter = lines[1...closing_index].join
48
+ body = lines[(closing_index + 1)..].join.strip
49
+
50
+ yaml = parse_frontmatter(frontmatter, path)
51
+ unless yaml.is_a?(Hash)
52
+ raise SkillError, "Empty or malformed YAML frontmatter#{location(path)}"
53
+ end
54
+
55
+ name = yaml["name"]
56
+ unless name.is_a?(String)
57
+ raise SkillError, "Missing or non-string `name` in frontmatter#{location(path)}"
58
+ end
59
+
60
+ description = yaml["description"]
61
+ unless description.is_a?(String)
62
+ raise SkillError, "Missing or non-string `description` in frontmatter#{location(path)}"
63
+ end
64
+
65
+ { name:, description:, body: }
66
+ end
67
+
68
+ #: (String frontmatter, String? path) -> untyped
69
+ def parse_frontmatter(frontmatter, path)
70
+ YAML.safe_load(frontmatter)
71
+ rescue Psych::Exception => e
72
+ raise SkillError, "Malformed YAML frontmatter#{location(path)}: #{e.message}"
73
+ end
74
+
75
+ #: (String?) -> String
76
+ def location(path)
77
+ path ? " in #{path}" : ""
78
+ end
79
+ end
80
+
81
+ #: (name: String, description: String, body: String) -> void
82
+ def initialize(name:, description:, body:)
83
+ @name = name.freeze
84
+ @description = description.freeze
85
+ @body = body.freeze
86
+ freeze
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubydex/skill"
4
+
5
+ module Rubydex
6
+ # The skills available on disk, keyed by id. `load` scans a directory for `<id>/SKILL.md` entries
7
+ # and records their paths.
8
+ # A skill file is only read when {#fetch} asks for it. Listing every description reads all skill files.
9
+ #
10
+ # The directory name is the id: it is what `rdx skill <id>` takes as a parameter.
11
+ class SkillRegistry
12
+ class << self
13
+ #: (String directory) -> Rubydex::SkillRegistry
14
+ def load(directory)
15
+ unless File.directory?(directory)
16
+ raise UnknownSkillDirectoryError, "Skill directory does not exist: #{directory}"
17
+ end
18
+
19
+ paths = {}
20
+ Dir.foreach(directory) do |entry|
21
+ next if entry == "." || entry == ".."
22
+
23
+ skill_path = File.join(directory, entry, "SKILL.md")
24
+ next unless File.file?(skill_path)
25
+
26
+ paths[entry] = skill_path
27
+ end
28
+
29
+ new(paths)
30
+ end
31
+ end
32
+
33
+ #: (Hash[String, String] paths) -> void
34
+ def initialize(paths)
35
+ @paths = paths.freeze
36
+ # Deliberately mutable inside a frozen registry: `freeze` protects the set of skills, while
37
+ # the cache only remembers what was already read from those files.
38
+ @cache = {} #: Hash[String, Skill]
39
+ freeze
40
+ end
41
+
42
+ #: -> Array[String]
43
+ def ids
44
+ @paths.keys.sort
45
+ end
46
+
47
+ #: (String id) -> Rubydex::Skill
48
+ def fetch(id)
49
+ @cache[id] ||= begin
50
+ path = @paths[id]
51
+ raise UnknownSkillError, "Unknown skill: #{id}" unless path
52
+
53
+ skill = Skill.load(path)
54
+ unless skill.name == id
55
+ raise SkillError, "Skill in #{path} declares `name: #{skill.name}` but lives in `#{id}`"
56
+ end
57
+
58
+ skill
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubydex
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
data/lib/rubydex.rb CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  require "bundler"
4
4
  require "uri"
5
+
6
+ # Files that must be loaded before the C extension
5
7
  require "rubydex/version"
6
8
  require "rubydex/mixin"
9
+ require "rubydex/severity"
7
10
 
8
11
  begin
9
12
  # Load the precompiled version of the library
@@ -16,8 +19,11 @@ end
16
19
 
17
20
  require "rubydex/errors"
18
21
  require "rubydex/failures"
22
+ require "rubydex/config"
19
23
  require "rubydex/location"
20
24
  require "rubydex/comment"
25
+ require "rubydex/rule"
26
+ require "rubydex/related_information"
21
27
  require "rubydex/diagnostic"
22
28
  require "rubydex/keyword"
23
29
  require "rubydex/keyword_parameter"
@@ -25,3 +31,5 @@ require "rubydex/graph"
25
31
  require "rubydex/declaration"
26
32
  require "rubydex/signature"
27
33
  require "rubydex/reference"
34
+
35
+ Dir.glob("#{__dir__}/rubydex/rules/**/*.rb").each { |file| require file }
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubydex
4
+ module Linter
5
+ module Rules
6
+ # Ensures discovered workspace linter rules follow these conventions:
7
+ #
8
+ # - A rule file does not define more than one linter rule.
9
+ # - Each rule subclass outside a test directory is in a rule directory.
10
+ # - Each checked rule subclass is in the `Rubydex::Linter::Rules` namespace.
11
+ #
12
+ # The rule directories are `rubydex_linter/rules/` and `lib/rubydex_linter/rules/`, including
13
+ # `lib/rubydex_linter/rules/` directories in nested gems.
14
+ # This rule does not report files in those directories that define no rule subclass.
15
+ class RuleStructure < CustomRule
16
+ include Helpers::SourceAccessHelpers
17
+
18
+ BASE_RULE_NAME = "Rubydex::Linter::CustomRule" #: String
19
+ RULE_NAMESPACE = "Rubydex::Linter::Rules" #: String
20
+ RULE_FILE_PATTERNS = [
21
+ "rubydex_linter/rules/**/*.rb",
22
+ "**/lib/rubydex_linter/rules/**/*.rb",
23
+ ].freeze #: Array[String]
24
+ TEST_FILE_PATTERNS = ["test/**/*", "**/test/**/*"].freeze #: Array[String]
25
+
26
+ class << self
27
+ # @override
28
+ #: -> singleton(Severity::Base)
29
+ def default_severity
30
+ Severity::Error
31
+ end
32
+ end
33
+
34
+ # @override
35
+ #: -> void
36
+ def lint
37
+ rules = child_classes(BASE_RULE_NAME)
38
+ rule_definitions_by_file = {} #: Hash[String, Hash[Rubydex::Class, Definition]]
39
+
40
+ rules.each do |rule|
41
+ rule_docs = []
42
+
43
+ rule.definitions.each do |rule_definition|
44
+ uri = rule_definition.document.uri
45
+ path = path_for_uri(uri)
46
+ next unless path_in_workspace?(path)
47
+
48
+ if rule_file?(path)
49
+ rule_definitions = (rule_definitions_by_file[uri] ||= {}) #: Hash[Rubydex::Class, Definition]
50
+ rule_definitions[rule] ||= rule_definition
51
+ elsif !test_file?(path)
52
+ report_wrong_rule_directory(rule.name, rule_definition)
53
+ end
54
+
55
+ rule_docs.concat(rule_definition.comments)
56
+ end
57
+
58
+ rule_definition = rule.definitions.find do |definition|
59
+ path = path_for_uri(definition.document.uri)
60
+ path_in_workspace?(path) && (rule_file?(path) || !test_file?(path))
61
+ end
62
+ next unless rule_definition
63
+
64
+ rule_name = rule.name
65
+
66
+ if rule_docs.empty?
67
+ add_diagnostic(
68
+ "`#{rule_name}` is missing documentation.",
69
+ diagnostic_location(rule_definition),
70
+ )
71
+ end
72
+
73
+ next if rule_name.start_with?("#{RULE_NAMESPACE}::")
74
+
75
+ report_wrong_rule_namespace(rule_name, rule_definition)
76
+ end
77
+
78
+ rule_definitions_by_file.each do |uri, rule_definitions|
79
+ next if rule_definitions.length <= 1
80
+
81
+ add_diagnostic(
82
+ "Each rule file must define only one linter rule; found #{rule_definitions.length}.",
83
+ file_location(uri),
84
+ related_information: rule_definitions.map do |rule, rule_definition|
85
+ RelatedInformation.new(
86
+ "`#{rule.name}` is defined here.",
87
+ diagnostic_location(rule_definition),
88
+ )
89
+ end,
90
+ )
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ #: (String, Definition) -> void
97
+ def report_wrong_rule_directory(rule_name, rule_definition)
98
+ add_diagnostic(
99
+ "`#{rule_name}` must be defined under `rubydex_linter/rules/` or `lib/rubydex_linter/rules/`.",
100
+ diagnostic_location(rule_definition),
101
+ )
102
+ end
103
+
104
+ #: (String, Definition) -> void
105
+ def report_wrong_rule_namespace(rule_name, rule_definition)
106
+ add_diagnostic(
107
+ "`#{rule_name}` must be defined under `#{RULE_NAMESPACE}`.",
108
+ diagnostic_location(rule_definition),
109
+ )
110
+ end
111
+
112
+ #: (String) -> bool
113
+ def path_in_workspace?(path)
114
+ workspace = graph.workspace_path
115
+ path == workspace || path.start_with?("#{workspace}/")
116
+ end
117
+
118
+ #: (String) -> bool
119
+ def rule_file?(path)
120
+ path_matches_patterns?(path, RULE_FILE_PATTERNS)
121
+ end
122
+
123
+ #: (String) -> bool
124
+ def test_file?(path)
125
+ path_matches_patterns?(path, TEST_FILE_PATTERNS)
126
+ end
127
+
128
+ #: (String, Array[String]) -> bool
129
+ def path_matches_patterns?(path, patterns)
130
+ Helpers::PathHelpers.path_matches_patterns?(
131
+ path,
132
+ patterns,
133
+ workspace: graph.workspace_path,
134
+ flags: Helpers::PathHelpers::EXCLUDE_FNMATCH_FLAGS,
135
+ )
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end