textmate_grammar 0.0.0

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/lib/textmate_grammar/generated/grammar.rb +32 -0
  4. data/lib/textmate_grammar/generated/rule.rb +144 -0
  5. data/lib/textmate_grammar/grammar.rb +670 -0
  6. data/lib/textmate_grammar/grammar_plugin.rb +189 -0
  7. data/lib/textmate_grammar/import_patterns.rb +14 -0
  8. data/lib/textmate_grammar/linters/flat_includes.rb +32 -0
  9. data/lib/textmate_grammar/linters/includes_then_tag_as.rb +48 -0
  10. data/lib/textmate_grammar/linters/standard_naming.rb +226 -0
  11. data/lib/textmate_grammar/linters/start_match_empty.rb +49 -0
  12. data/lib/textmate_grammar/linters/tests.rb +19 -0
  13. data/lib/textmate_grammar/linters/unused_unresolved.rb +9 -0
  14. data/lib/textmate_grammar/pattern_extensions/look_ahead_for.rb +32 -0
  15. data/lib/textmate_grammar/pattern_extensions/look_ahead_to_avoid.rb +31 -0
  16. data/lib/textmate_grammar/pattern_extensions/look_behind_for.rb +31 -0
  17. data/lib/textmate_grammar/pattern_extensions/look_behind_to_avoid.rb +31 -0
  18. data/lib/textmate_grammar/pattern_extensions/lookaround_pattern.rb +169 -0
  19. data/lib/textmate_grammar/pattern_extensions/match_result_of.rb +67 -0
  20. data/lib/textmate_grammar/pattern_extensions/maybe.rb +50 -0
  21. data/lib/textmate_grammar/pattern_extensions/one_of.rb +107 -0
  22. data/lib/textmate_grammar/pattern_extensions/one_or_more_of.rb +42 -0
  23. data/lib/textmate_grammar/pattern_extensions/or_pattern.rb +55 -0
  24. data/lib/textmate_grammar/pattern_extensions/placeholder.rb +102 -0
  25. data/lib/textmate_grammar/pattern_extensions/recursively_match.rb +76 -0
  26. data/lib/textmate_grammar/pattern_extensions/zero_or_more_of.rb +50 -0
  27. data/lib/textmate_grammar/pattern_variations/base_pattern.rb +870 -0
  28. data/lib/textmate_grammar/pattern_variations/legacy_pattern.rb +61 -0
  29. data/lib/textmate_grammar/pattern_variations/pattern.rb +9 -0
  30. data/lib/textmate_grammar/pattern_variations/pattern_range.rb +233 -0
  31. data/lib/textmate_grammar/pattern_variations/repeatable_pattern.rb +204 -0
  32. data/lib/textmate_grammar/regex_operator.rb +182 -0
  33. data/lib/textmate_grammar/regex_operators/alternation.rb +24 -0
  34. data/lib/textmate_grammar/regex_operators/concat.rb +23 -0
  35. data/lib/textmate_grammar/stdlib/common.rb +20 -0
  36. data/lib/textmate_grammar/tokens.rb +110 -0
  37. data/lib/textmate_grammar/transforms/add_ending.rb +25 -0
  38. data/lib/textmate_grammar/transforms/bailout.rb +92 -0
  39. data/lib/textmate_grammar/transforms/fix_repeated_tag_as.rb +75 -0
  40. data/lib/textmate_grammar/transforms/resolve_placeholders.rb +121 -0
  41. data/lib/textmate_grammar/util.rb +198 -0
  42. data/lib/textmate_grammar.rb +4 -0
  43. metadata +85 -0
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @abstract Subclass GrammarLinter or GrammarTransform to implement a plugin
4
+ class GrammarPlugin
5
+ # The options this plugin supports
6
+ # @return [Array<Symbol>] a list of symbols that represent keys that can
7
+ # be read by the plugin
8
+ # @note the keys :grammar and :repository are reserved for passing grammar information
9
+ def self.options
10
+ []
11
+ end
12
+
13
+ # display the options as they would appear in the source
14
+ # @abstract override this to display the options as they would be entered into
15
+ # source code
16
+ # @param indent [String] the spaces to indent each line width
17
+ # @param options [Hash] all options passed to the pattern
18
+ # @return [String] the options as a string
19
+ # @note each option should be prepended with +"\\n,#{indent}"+
20
+ # @note only display the options that are unique to this plugin
21
+ def self.display_options(indent, options) # rubocop:disable Lint/UnusedMethodArgument
22
+ raise "Internal error: display_options called with no provided options" if options.empty?
23
+
24
+ if self.options.empty?
25
+ raise "Internal error: display_options called on a plugin that provides no options"
26
+ end
27
+
28
+ raise "GrammarPlugin::options implemented but GrammarPlugin::display_options has not been"
29
+ end
30
+ end
31
+
32
+ # @abstract Subclass and override {#pre_lint} and/or {#post_lint}
33
+ # to implement a linter
34
+ class GrammarLinter < GrammarPlugin
35
+ #
36
+ # Runs the linter on each pattern
37
+ #
38
+ # @param pattern [PatternBase, Symbol, Hash] the pattern to lint
39
+ # @param options [Hash] hash of any of the option keys provided by self.options.
40
+ # options will only be populated when pattern is a PatternBase
41
+ #
42
+ # @return [Boolean] the result of the lint
43
+ def pre_lint(pattern, options = {}) # rubocop:disable Lint/UnusedMethodArgument
44
+ true
45
+ end
46
+
47
+ #
48
+ # Runs the linter on the entire grammar
49
+ #
50
+ # @param [Hash] grammar_hash The entire grammar
51
+ #
52
+ # @return [Boolean] the result og the lint
53
+ #
54
+ def post_lint(grammar_hash) # rubocop:disable Lint/UnusedMethodArgument
55
+ true
56
+ end
57
+ end
58
+
59
+ # @abstract Subclass and override {#pre_transform} and/or {#post_transform}
60
+ # to implement a transformation
61
+ class GrammarTransform < GrammarPlugin
62
+ #
63
+ # Preforms the transformation on each pattern
64
+ #
65
+ # @param pattern [PatternBase, Symbol, Hash] the pattern to transform
66
+ # @param options [Hash] hash of any of the option keys provided by self.options.
67
+ # options will only be populated when pattern is a PatternBase
68
+ #
69
+ # @return [PatternBase, Symbol, Hash] The transformed pattern. The return type should
70
+ # match the type of pattern
71
+ #
72
+ # @note pattern should not be modified
73
+ #
74
+ def pre_transform(pattern, options) # rubocop:disable Lint/UnusedMethodArgument
75
+ pattern
76
+ end
77
+
78
+ #
79
+ # Performs the transformation on the whole grammar
80
+ #
81
+ # @param [Hash] grammar_hash The entire grammar
82
+ #
83
+ # @return [Hash] The transformed grammar
84
+ #
85
+ # @note grammar_hash should not be modified
86
+ #
87
+ def post_transform(grammar_hash)
88
+ grammar_hash
89
+ end
90
+ end
91
+
92
+ class Grammar
93
+ @@linters = []
94
+ @@transforms = {
95
+ before_pre_linter: [],
96
+ after_pre_linter: [],
97
+ before_post_linter: [],
98
+ after_post_linter: [],
99
+ }
100
+
101
+ #
102
+ # Register a linter plugin
103
+ #
104
+ # @param [GrammarLinter] linter the linter plugin
105
+ #
106
+ # @return [void] nothing
107
+ #
108
+ def self.register_linter(linter)
109
+ @@linters << linter
110
+ end
111
+
112
+ #
113
+ # Register a transformation plugin
114
+ #
115
+ # @param [GrammarTransform] transform the transformation plugin
116
+ # @param [Numeric] priority an optional priority
117
+ #
118
+ # @note The priority controls when a transformation runs in relation to
119
+ # other events in addition to ordering transformations
120
+ # priorities < 100 have their pre transform run before pre linters
121
+ # priorities >= 100 have their pre transform run after pre linters
122
+ # priorities >= 200 do not have their pre_transform function ran
123
+ # priorities < 300 have their post transorm run before post linters
124
+ # priorities >= 300 have their post transorm run before post linters
125
+ #
126
+ # @return [void] nothing
127
+ #
128
+ def self.register_transform(transform, priority = 150)
129
+ key = if priority < 100 then :before_pre_linter
130
+ elsif priority < 200 then :after_pre_linter
131
+ elsif priority < 300 then :before_post_linter
132
+ else :after_pre_linter
133
+ end
134
+
135
+ @@transforms[key] << {
136
+ priority: priority,
137
+ transform: transform,
138
+ }
139
+ end
140
+
141
+ #
142
+ # Gets all registered plugins
143
+ #
144
+ # @api private
145
+ #
146
+ # @return [Array<GrammarPlugin>] A list of all plugins
147
+ #
148
+ def self.plugins
149
+ @@linters + @@transforms.values.flatten.map { |v| v[:transform] }
150
+ end
151
+
152
+ #
153
+ # Removes a plugin whose classname matches plugin
154
+ #
155
+ # @param [#to_s] plugin The plugin name to remove
156
+ #
157
+ # @return [void]
158
+ #
159
+ def self.remove_plugin(plugin)
160
+ @@linters.delete_if { |linter| linter.class.to_s == plugin.to_s }
161
+ @@transforms[:before_pre_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
162
+ @@transforms[:after_pre_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
163
+ @@transforms[:before_post_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
164
+ @@transforms[:after_post_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
165
+ end
166
+ end
167
+
168
+ #
169
+ # Filters a {PatternBase#original_arguments} to just the options required for a plugin
170
+ #
171
+ # @api private
172
+ #
173
+ # @param [GrammarPlugin] plugin The plugin to filter options
174
+ # @param [PatternBase, Symbol, Hash] pattern the pattern with options to filter
175
+ # @param [Hash] default the default options to supply to the plugin
176
+ #
177
+ # @return [Hash] the filtered options
178
+ #
179
+ def filter_options(plugin, pattern, default)
180
+ options = {}
181
+ if pattern.is_a? PatternBase
182
+ options = pattern.original_arguments.select { |k| plugin.class.options.include? k }
183
+ end
184
+ options.merge(default)
185
+ end
186
+
187
+ # load default linters and transforms
188
+ Dir[File.join(__dir__, 'linters', '*.rb')].each { |file| require file }
189
+ Dir[File.join(__dir__, 'transforms', '*.rb')].each { |file| require file }
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deep_clone'
4
+ require 'yaml'
5
+ require 'textmate_grammar/grammar_plugin'
6
+ require 'textmate_grammar/util'
7
+ require 'textmate_grammar/regex_operator'
8
+ require 'textmate_grammar/regex_operators/concat'
9
+ require 'textmate_grammar/tokens'
10
+
11
+ # import Pattern, LegacyPattern, and PatternRange
12
+ Dir[File.join(__dir__, 'pattern_variations', '*.rb')].each { |file| require file }
13
+ # import .or(), .maybe(), .zeroOrMoreOf(), etc
14
+ Dir[File.join(__dir__, 'pattern_extensions', '*.rb')].each { |file| require file }
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'textmate_grammar/util'
4
+
5
+ #
6
+ # Internal check, ensures that :includes is nil or a flat array
7
+ #
8
+ class FlatIncludes < GrammarLinter
9
+ #
10
+ # (see GrammarLinter#pre_lint)
11
+ #
12
+ def pre_lint(pattern, options)
13
+ return true unless pattern.is_a? PatternBase
14
+ return true if pattern.arguments[:includes].nil?
15
+
16
+ if pattern.arguments[:includes].is_a?(Array) &&
17
+ pattern.arguments[:includes].none? { |v| v.is_a? Array }
18
+ flat = true
19
+ pattern.arguments[:includes].map do |s|
20
+ flat = false unless pre_lint(s, options)
21
+ end
22
+ return flat
23
+ end
24
+
25
+ puts "The pattern `#{pattern.name}' does not have a flat includes array."
26
+ puts "This is an internal error"
27
+
28
+ false
29
+ end
30
+ end
31
+
32
+ Grammar.register_linter(FlatIncludes.new)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'textmate_grammar/util'
4
+
5
+ #
6
+ # A pattern that has an includes cannot have any tag_as in @match
7
+ #
8
+ class IncludesThenTagAs < GrammarLinter
9
+ #
10
+ # Does pattern or any of its children / siblings have a tag_as
11
+ #
12
+ # @param [PatternBase, String] pattern the pattern to check
13
+ #
14
+ # @return [Boolean] if any of the patterns have a tag_as
15
+ #
16
+ def tag_as?(pattern)
17
+ return false unless pattern.is_a? PatternBase
18
+
19
+ pattern.each do |s|
20
+ puts s.arguments[:tag_as] if s.arguments[:tag_as]
21
+ return true if s.arguments[:tag_as]
22
+ end
23
+
24
+ false
25
+ end
26
+
27
+ #
28
+ # (see GrammarLinter#pre_lint)
29
+ #
30
+ def pre_lint(pattern, options)
31
+ return true unless pattern.is_a? PatternBase
32
+ return true if pattern.is_a? PatternRange
33
+
34
+ return false unless pre_lint(pattern.match, options)
35
+
36
+ return true unless pattern.arguments[:includes].is_a? Array
37
+ return true unless tag_as?(pattern.match)
38
+
39
+ name = pattern.name
40
+ puts "The pattern `#{name}' has both an includes argument and a match argument that,"
41
+ puts "it or a sub pattern has a tag_as argument."
42
+ puts "this is not supported"
43
+
44
+ true # TODO: fix issue
45
+ end
46
+ end
47
+
48
+ Grammar.register_linter(IncludesThenTagAs.new)
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Checks for names to match expected naming format
5
+ # see https://www.sublimetext.com/docs/3/scope_naming.html
6
+ #
7
+
8
+ class StandardNaming < GrammarLinter
9
+ # This is a summarization of the rules on the sublime text website for scope name
10
+ # process:
11
+ # 1. start at root
12
+ # 2. for each component in name
13
+ # 1. if there exists a key with that name
14
+ # 1. if the value is a hash: repeat with next component and that hash
15
+ # 2. otherwise return the value
16
+ # 2. else
17
+ # 1. if there exists a key of "*" return true, otherwise false
18
+ #
19
+ # to ease parsing, there should only every be one valid path
20
+ # if "a" exists, dont add "a.b" to the same key
21
+
22
+ # @return [Hash] a summarization of expected names
23
+ EXPECTED_NAMES = {
24
+ "comment" => {
25
+ "line" => true,
26
+ "block" => true,
27
+ }.freeze,
28
+ "constant" => {
29
+ "numeric" => true,
30
+ "language" => true,
31
+ "character" => {"escape" => true}.freeze,
32
+ "other" => true,
33
+ }.freeze,
34
+ "entity" => {
35
+ "name" => {
36
+ "class" => true,
37
+ "struct" => true,
38
+ "enum" => true,
39
+ "union" => true,
40
+ "trait" => true,
41
+ "interface" => true,
42
+ "impl" => true,
43
+ "type" => true,
44
+ "function" => true,
45
+ "namespace" => true,
46
+ "constant" => true,
47
+ "label" => true,
48
+ "section" => true,
49
+ "scope-resolution" => true,
50
+ "tag" => true,
51
+ "attribute-name" => true,
52
+ "other" => true,
53
+ }.freeze,
54
+ "other" => true,
55
+ }.freeze,
56
+ "invalid" => {
57
+ "illegal" => true,
58
+ "deprecated" => true,
59
+ "unknown" => true,
60
+ }.freeze,
61
+ "keyword" => {
62
+ "control" => true,
63
+ "operator" => true,
64
+ "other" => true,
65
+ "declaration" => true,
66
+ }.freeze,
67
+ "markup" => {
68
+ "heading" => true,
69
+ "list" => {
70
+ "numbered" => true,
71
+ "unnumbered" => true,
72
+ }.freeze,
73
+ "bold" => true,
74
+ "italic" => true,
75
+ "inline" => true,
76
+ "underline" => true,
77
+ "inserted" => true,
78
+ "deleted" => true,
79
+ "quote" => true,
80
+ "raw" => {
81
+ "inline" => true,
82
+ "block" => true,
83
+ }.freeze,
84
+ "other" => true,
85
+ }.freeze,
86
+ "meta" => {
87
+ "asm" => true,
88
+ "class" => true,
89
+ "struct" => true,
90
+ "enum" => true,
91
+ "union" => true,
92
+ "trait" => true,
93
+ "interface" => true,
94
+ "declaration" => true,
95
+ "impl" => true,
96
+ "initialization" => true,
97
+ "type" => true,
98
+ "qualified_type" => true,
99
+ "function" => true,
100
+ "parameter" => true,
101
+ "namespace" => true,
102
+ "using-namespace" => true,
103
+ "preprocessor" => true,
104
+ "conditional" => true,
105
+ "annotation" => true,
106
+ "path" => true,
107
+ "function-call" => true,
108
+ "block" => true,
109
+ "group" => true,
110
+ "braces" => true,
111
+ "parens" => true,
112
+ "brackets" => true,
113
+ "generic" => true,
114
+ "template" => true,
115
+ "tag" => true,
116
+ "paragraph" => true,
117
+ "string" => true,
118
+ "interpolation" => true,
119
+ "toc-list" => true,
120
+ "banner" => true,
121
+ }.freeze,
122
+ "punctuation" => {
123
+ "definition" => true,
124
+ "separator" => true,
125
+ "terminator" => true,
126
+ "accessor" => true,
127
+ "section" => true,
128
+ "vararg-ellipses" => true,
129
+ }.freeze,
130
+ "source" => true,
131
+ "storage" => {
132
+ "type" => true,
133
+ "modifier" => true,
134
+ }.freeze,
135
+ "string" => {
136
+ "quoted" => {
137
+ "single" => true,
138
+ "double" => true,
139
+ "other" => true,
140
+ }.freeze,
141
+ "unquoted" => true,
142
+ "regexp" => true,
143
+ }.freeze,
144
+ "support" => {
145
+ "constant" => true,
146
+ "function" => true,
147
+ "module" => true,
148
+ "type" => true,
149
+ "class" => true,
150
+ "other" => true,
151
+ }.freeze,
152
+ "text" => true,
153
+ "variable" => {
154
+ "other" => true,
155
+ "language" => true,
156
+ "function" => true,
157
+ "annotation" => true,
158
+ "parameter" => true,
159
+ }.freeze,
160
+ }.freeze
161
+
162
+ #
163
+ # Checks the tag keys at this level
164
+ #
165
+ # @param [Array<string>] tag an array of tag components
166
+ # @param [Numeric] index The index into tag to check
167
+ # @param [Hash] root the hash to check against
168
+ #
169
+ # @return [Boolean] If this is a valid tag
170
+ #
171
+ def recursive_check_tag(tag, index = 0, root = EXPECTED_NAMES)
172
+ if root.has_key?(tag[index])
173
+ next_part = root[tag[index]]
174
+ return recursive_check_tag(tag, index+1, next_part) if next_part.is_a? Hash
175
+
176
+ return [next_part, index, root]
177
+ elsif root.has_key? "*"
178
+ return [root["*"], index, root]
179
+ end
180
+
181
+ [false, index, root]
182
+ end
183
+
184
+ #
185
+ # Checks a tag for standard naming scheme
186
+ #
187
+ # @param [String] tag the tag to check
188
+ #
189
+ # @return [void] nothing
190
+ #
191
+ def check_tag(tag)
192
+ result, pos, root = recursive_check_tag(tag)
193
+ return if result
194
+
195
+ valid_prefix = (pos > 0) ? tag[0..(pos-1)].join(".") + "." : ""
196
+
197
+ puts "The prefix `#{tag[0..pos].join('.')}' does not follow the standard format"
198
+ puts "The expected prefixes at this level are:"
199
+ root.keys.each do |key|
200
+ if root[:key] == false
201
+ puts "- #{valid_prefix}#{key}"
202
+ else
203
+ puts " #{valid_prefix}#{key}"
204
+ end
205
+ end
206
+ end
207
+
208
+ #
209
+ # Checks for names to match expected naming format
210
+ #
211
+ # @return [True] warnings to not return false
212
+ #
213
+ def pre_lint(pattern, _options)
214
+ return true unless pattern.is_a? PatternBase
215
+
216
+ pattern.each(true) do |pat|
217
+ next unless pat.arguments[:tag_as]
218
+
219
+ pat.arguments[:tag_as].split(" ").each { |tag| check_tag(tag.split(".")) }
220
+ end
221
+
222
+ true
223
+ end
224
+ end
225
+
226
+ Grammar.register_linter(StandardNaming.new)