@appthreat/atom-parsetools 1.2.2 → 1.4.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.
- package/README.md +118 -7
- package/astgen.js +297 -57
- package/package.json +9 -6
- package/plugins/composer/installed.php +6 -6
- package/plugins/rubyastgen/Gemfile +6 -4
- package/plugins/rubyastgen/Gemfile.lock +5 -8
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-4ac84327b3af/Gemfile +11 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-4ac84327b3af/exe/ruby_ast_gen +55 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-4ac84327b3af/lib/ruby_ast_gen/cli.rb +133 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-4ac84327b3af/lib/ruby_ast_gen/node_handling.rb +449 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/{ruby_ast_gen-cc312ebf06b4 → ruby_ast_gen-4ac84327b3af}/lib/ruby_ast_gen/version.rb +1 -1
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-4ac84327b3af/lib/ruby_ast_gen.rb +888 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-4ac84327b3af/ruby_ast_gen.gemspec +25 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/current.rb +1 -1
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/version.rb +1 -1
- package/plugins/rubyastgen/bundle/ruby/4.0.0/specifications/ast-2.4.3.gemspec +29 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/specifications/bundler-2.6.9.gemspec +25 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/specifications/ostruct-0.6.3.gemspec +22 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/specifications/parser-3.3.12.0.gemspec +37 -0
- package/rbastgen.js +102 -13
- package/scalasem.js +11 -2
- package/plugins/bin/bundler +0 -14
- package/plugins/bin/ruby_ast_gen +0 -14
- package/plugins/rubyastgen/bundle/bundler/setup.rb +0 -44
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-cc312ebf06b4/exe/ruby_ast_gen +0 -63
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-cc312ebf06b4/lib/ruby_ast_gen/node_handling.rb +0 -269
- package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/ruby_ast_gen-cc312ebf06b4/lib/ruby_ast_gen.rb +0 -218
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/prism-1.9.0/gem.build_complete +0 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/prism-1.9.0/gem_make.out +0 -44
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/prism-1.9.0/mkmf.log +0 -74
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/prism-1.9.0/prism/prism.so +0 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/racc-1.8.1/gem.build_complete +0 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/racc-1.8.1/gem_make.out +0 -18
- package/plugins/rubyastgen/bundle/ruby/4.0.0/extensions/x86_64-linux/4.0.0/racc-1.8.1/racc/cparse.so +0 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/BSDmakefile +0 -58
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/Makefile +0 -116
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism/Makefile +0 -273
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism/api_node.c +0 -6945
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism/api_pack.c +0 -276
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism/extconf.rb +0 -127
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism/extension.c +0 -1427
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism/extension.h +0 -19
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/ast.h +0 -8254
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/defines.h +0 -260
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/diagnostic.h +0 -458
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/encoding.h +0 -283
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/node.h +0 -129
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/options.h +0 -488
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/pack.h +0 -163
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/parser.h +0 -936
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/prettyprint.h +0 -34
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/regexp.h +0 -43
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/static_literals.h +0 -121
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_buffer.h +0 -236
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_char.h +0 -204
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_constant_pool.h +0 -218
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_integer.h +0 -130
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_list.h +0 -103
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_memchr.h +0 -29
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_newline_list.h +0 -113
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_string.h +0 -200
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_strncasecmp.h +0 -32
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/util/pm_strpbrk.h +0 -46
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism/version.h +0 -29
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/include/prism.h +0 -408
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/compiler.rb +0 -801
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/desugar_compiler.rb +0 -392
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/dispatcher.rb +0 -2210
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/dot_visitor.rb +0 -4767
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/dsl.rb +0 -1003
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/ffi.rb +0 -578
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/inspect_visitor.rb +0 -2393
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/lex_compat.rb +0 -911
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/mutation_compiler.rb +0 -772
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/node.rb +0 -19966
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/node_ext.rb +0 -511
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/pack.rb +0 -230
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/parse_result/comments.rb +0 -188
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/parse_result/errors.rb +0 -66
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/parse_result/newlines.rb +0 -155
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/parse_result.rb +0 -907
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/pattern.rb +0 -269
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/polyfill/append_as_bytes.rb +0 -15
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/polyfill/byteindex.rb +0 -13
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/polyfill/scan_byte.rb +0 -14
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/polyfill/unpack1.rb +0 -14
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/polyfill/warn.rb +0 -36
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/prism.so +0 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/reflection.rb +0 -416
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/relocation.rb +0 -505
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/serialize.rb +0 -2400
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/string_query.rb +0 -31
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/parser/builder.rb +0 -62
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/parser/compiler.rb +0 -2234
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/parser/lexer.rb +0 -820
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/parser.rb +0 -376
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/parser_current.rb +0 -26
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/parser_versions.rb +0 -36
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/ripper/filter.rb +0 -53
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/ripper/lexer.rb +0 -135
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/ripper/sexp.rb +0 -126
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/ripper/shim.rb +0 -5
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/ripper.rb +0 -3520
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation/ruby_parser.rb +0 -1964
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/translation.rb +0 -18
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism/visitor.rb +0 -813
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/lib/prism.rb +0 -98
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/compiler.rbi +0 -12
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/dsl.rbi +0 -524
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/inspect_visitor.rbi +0 -12
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/node.rbi +0 -8750
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/node_ext.rbi +0 -107
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/parse_result.rbi +0 -404
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/reflection.rbi +0 -58
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/string_query.rbi +0 -12
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/translation/parser.rbi +0 -11
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/translation/parser_versions.rbi +0 -23
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/translation/ripper.rbi +0 -15
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism/visitor.rbi +0 -473
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/rbi/prism.rbi +0 -63
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/compiler.rbs +0 -9
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/dispatcher.rbs +0 -19
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/dot_visitor.rbs +0 -6
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/dsl.rbs +0 -351
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/inspect_visitor.rbs +0 -22
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/lex_compat.rbs +0 -10
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/mutation_compiler.rbs +0 -159
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/node.rbs +0 -4042
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/node_ext.rbs +0 -149
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/pack.rbs +0 -43
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/parse_result/comments.rbs +0 -38
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/parse_result.rbs +0 -197
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/pattern.rbs +0 -13
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/reflection.rbs +0 -50
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/relocation.rbs +0 -185
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/serialize.rbs +0 -8
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/string_query.rbs +0 -11
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism/visitor.rbs +0 -169
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/sig/prism.rbs +0 -272
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/diagnostic.c +0 -854
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/encoding.c +0 -5340
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/node.c +0 -8685
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/options.c +0 -338
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/pack.c +0 -509
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/prettyprint.c +0 -8957
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/prism.c +0 -22679
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/regexp.c +0 -790
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/serialize.c +0 -2274
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/static_literals.c +0 -617
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/token_type.c +0 -703
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_buffer.c +0 -357
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_char.c +0 -318
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_constant_pool.c +0 -342
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_integer.c +0 -670
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_list.c +0 -49
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_memchr.c +0 -35
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_newline_list.c +0 -125
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_string.c +0 -381
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_strncasecmp.c +0 -36
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/prism-1.9.0/src/util/pm_strpbrk.c +0 -206
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/BSDL +0 -22
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/COPYING +0 -56
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ChangeLog +0 -846
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/README.ja.rdoc +0 -58
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/README.rdoc +0 -60
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/TODO +0 -5
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/bin/racc +0 -320
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse/Makefile +0 -273
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse/cparse.c +0 -840
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse/extconf.rb +0 -8
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/compat.rb +0 -33
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/cparse.so +0 -0
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/debugflags.rb +0 -60
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/exception.rb +0 -16
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/grammar.rb +0 -1191
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/grammarfileparser.rb +0 -667
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/info.rb +0 -18
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/iset.rb +0 -92
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/logfilegenerator.rb +0 -212
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/parser-text.rb +0 -644
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/parser.rb +0 -630
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/parserfilegenerator.rb +0 -473
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/sourcetext.rb +0 -35
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/state.rb +0 -976
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/statetransitiontable.rb +0 -311
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc/static.rb +0 -5
- package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/racc-1.8.1/lib/racc.rb +0 -6
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/{ruby_ast_gen-cc312ebf06b4 → ruby_ast_gen-4ac84327b3af}/.rspec +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/{ruby_ast_gen-cc312ebf06b4 → ruby_ast_gen-4ac84327b3af}/bin/console +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/{ruby_ast_gen-cc312ebf06b4 → ruby_ast_gen-4ac84327b3af}/bin/setup +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/bundler/gems/{ruby_ast_gen-cc312ebf06b4 → ruby_ast_gen-4ac84327b3af}/update_version.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/bin/ruby-parse +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/bin/ruby-rewrite +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/gauntlet_parser.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/all.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ast/node.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ast/processor.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/base.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/builders/default.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/clobbering_error.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/color.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/context.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/current_arg_stack.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/deprecation.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/diagnostic/engine.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/diagnostic.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer/dedenter.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer/explanation.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer/literal.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer/stack_state.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer-F0.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer-F1.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/lexer-strings.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/macruby.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/max_numparam_stack.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/messages.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/meta.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/rewriter.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby18.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby19.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby20.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby21.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby22.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby23.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby24.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby25.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby26.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby27.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby30.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby31.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby32.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby33.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/ruby34.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/rubymotion.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/runner/ruby_parse.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/runner/ruby_rewrite.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/runner.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/buffer.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/comment/associator.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/comment.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/collection.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/condition.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/constant.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/definition.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/for.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/heredoc.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/index.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/keyword.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/method_definition.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/objc_kwarg.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/operator.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/rescue_body.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/send.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/ternary.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map/variable.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/map.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/range.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/rewriter/action.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/rewriter.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/tree_rewriter/action.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/source/tree_rewriter.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/static_environment.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/syntax_error.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/tree_rewriter.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/unknown_encoding_in_magic_comment_error.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser/variables_stack.rb +0 -0
- /package/plugins/rubyastgen/bundle/ruby/4.0.0/gems/{parser-3.3.11.1 → parser-3.3.12.0}/lib/parser.rb +0 -0
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'thread'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
require_relative 'ruby_ast_gen/version'
|
|
7
|
+
require_relative 'ruby_ast_gen/node_handling'
|
|
8
|
+
require_relative 'ruby_ast_gen/cli'
|
|
9
|
+
|
|
10
|
+
module RubyAstGen
|
|
11
|
+
|
|
12
|
+
# All human-facing output goes through here, on stdout (chen captures it). Messages print when
|
|
13
|
+
# their rank is at or above the configured level's rank, so the default :info keeps DEBUG quiet
|
|
14
|
+
# instead of one line per file (plan 03 §10).
|
|
15
|
+
module Logger
|
|
16
|
+
|
|
17
|
+
LEVEL_RANKS = { debug: 0, info: 1, warn: 2, error: 3 }.freeze
|
|
18
|
+
private_constant :LEVEL_RANKS
|
|
19
|
+
|
|
20
|
+
def self.level
|
|
21
|
+
@level ||= :info
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.level=(value)
|
|
25
|
+
normalized = value.to_s.downcase.to_sym
|
|
26
|
+
if LEVEL_RANKS.key?(normalized)
|
|
27
|
+
@level = normalized
|
|
28
|
+
else
|
|
29
|
+
# A mistyped level is a configuration mistake, not a usage error: fall back to :info
|
|
30
|
+
# rather than failing the run.
|
|
31
|
+
@level = :info
|
|
32
|
+
warn "Unknown log level #{value.inspect}; using :info."
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Restores the default level. The level is process-global mutable state, so the test suite
|
|
37
|
+
# resets it after every example.
|
|
38
|
+
def self.reset_level!
|
|
39
|
+
@level = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.debug(message)
|
|
43
|
+
puts "[DEBUG] #{message}" if enabled?(:debug)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.info(message)
|
|
47
|
+
puts "[INFO] #{message}" if enabled?(:info)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.warn(message)
|
|
51
|
+
puts "[WARN] #{message}" if enabled?(:warn)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.error(message)
|
|
55
|
+
puts "[ERR] #{message}" if enabled?(:error)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.enabled?(message_level)
|
|
59
|
+
LEVEL_RANKS[level] <= LEVEL_RANKS[message_level]
|
|
60
|
+
end
|
|
61
|
+
private_class_method :enabled?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Main method to parse the input and generate the AST output
|
|
65
|
+
# Errors a single file may raise without ending the run: the consumer reads a non-zero exit as
|
|
66
|
+
# "no files were parsed at all", so a per-file problem must never become one.
|
|
67
|
+
#
|
|
68
|
+
# SystemStackError is listed deliberately — it is not a StandardError, and raised inside a worker
|
|
69
|
+
# thread it kills the thread and silently abandons the rest of its queue. JRuby needs one more:
|
|
70
|
+
# when the stack runs out inside a *Java* frame (prism's node loading, which is where a deeply
|
|
71
|
+
# nested literal lands) it raises java.lang.StackOverflowError, which is neither of the two. That
|
|
72
|
+
# escaped every rescue here, aborted the run and exited non-zero — the failure mode this list
|
|
73
|
+
# exists to prevent. Measured ceilings vary by runtime and by thread: the prism translation gives
|
|
74
|
+
# up at ~5000 nested literals on CRuby's main thread but at ~250 in a JRuby worker with a 512 KB
|
|
75
|
+
# stack, and serialization has its own ceiling (see .serialize) — all of them degrade to
|
|
76
|
+
# log + skip.
|
|
77
|
+
PER_FILE_ERRORS = begin
|
|
78
|
+
errors = [StandardError, SystemStackError]
|
|
79
|
+
errors << Java::JavaLang::StackOverflowError if RUBY_PLATFORM == "java"
|
|
80
|
+
errors.freeze
|
|
81
|
+
rescue NameError
|
|
82
|
+
[StandardError, SystemStackError].freeze
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Side-records of a run. Both live *inside* the output directory and both end in `.jsonl`
|
|
86
|
+
# on purpose: chen feeds every file ending in `.json` to its JSON AST reader, which requires
|
|
87
|
+
# a top-level file_path (plan 04 §10.2), so a `.json` side-record would silently miscount
|
|
88
|
+
# every run (plan 03 §3/§4).
|
|
89
|
+
DIAGNOSTICS_FILENAME = "ruby_ast_gen_diagnostics.jsonl"
|
|
90
|
+
MANIFEST_FILENAME = "ruby_ast_gen_manifest.jsonl"
|
|
91
|
+
|
|
92
|
+
# Per-file warning entries kept in the top-level diagnostics array before the rest are cut
|
|
93
|
+
# (diagnostics_truncated marks the cut). The full list stays on stdout for -l debug readers.
|
|
94
|
+
DIAGNOSTICS_CAP = 50
|
|
95
|
+
|
|
96
|
+
def self.parse(opts)
|
|
97
|
+
# Apply the level before anything can log, and specifically before the worker threads in
|
|
98
|
+
# process_directory are spawned: they inherit whatever is configured here.
|
|
99
|
+
Logger.level = :debug if opts[:debug]
|
|
100
|
+
Logger.level = opts[:log_level] if opts[:log_level]
|
|
101
|
+
|
|
102
|
+
Logger.debug "CLI Arguments received: #{opts}"
|
|
103
|
+
|
|
104
|
+
input_path = opts[:input]
|
|
105
|
+
output_dir = opts[:output]
|
|
106
|
+
exclude_regex = validated_exclude(opts[:exclude])
|
|
107
|
+
parser_target = validated_parser_target(opts[:parser_target])
|
|
108
|
+
max_depth = validated_max_depth(opts[:max_depth])
|
|
109
|
+
threads = validated_threads(opts[:threads])
|
|
110
|
+
|
|
111
|
+
raise ArgumentError, "Error: '-i' or '--input' is required." if input_path.nil?
|
|
112
|
+
|
|
113
|
+
Logger.debug "Exclude Regex Received: #{exclude_regex}"
|
|
114
|
+
|
|
115
|
+
FileUtils.mkdir_p(output_dir)
|
|
116
|
+
|
|
117
|
+
# Per-run aggregate, shared by the worker threads. The mutex guards every update: each
|
|
118
|
+
# counter would otherwise race across the 10 workers, and the truncation counter in
|
|
119
|
+
# NodeHandling is per-file state for exactly this reason. No module-level state, so
|
|
120
|
+
# concurrent or repeated parses cannot pollute each other.
|
|
121
|
+
state = {
|
|
122
|
+
mutex: Mutex.new, parsed: 0, failed: 0, skipped_nonruby: 0, excluded: 0,
|
|
123
|
+
truncated_files: 0, parse_errors: []
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if File.file?(input_path)
|
|
127
|
+
process_file(input_path, output_dir, exclude_regex, input_path, parser_target: parser_target,
|
|
128
|
+
max_depth: max_depth, state: state)
|
|
129
|
+
elsif File.directory?(input_path)
|
|
130
|
+
process_directory(input_path, output_dir, exclude_regex, threads, parser_target: parser_target,
|
|
131
|
+
max_depth: max_depth, state: state)
|
|
132
|
+
else
|
|
133
|
+
# Usage errors are raised, not logged-and-exited: the caller decides the exit status, and
|
|
134
|
+
# the message must not be silenceable by --log (it is the only output of a failed run).
|
|
135
|
+
raise ArgumentError, "#{input_path} is neither a file nor a directory."
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The manifest is both written and returned: the CLI reads files_failed off it to implement
|
|
139
|
+
# --fail-on-error.
|
|
140
|
+
manifest = manifest_for(input_path, output_dir, state, threads: threads, max_depth: max_depth,
|
|
141
|
+
parser_target: parser_target)
|
|
142
|
+
write_run_records(output_dir, state, manifest)
|
|
143
|
+
manifest
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
# Raises rather than exiting: this is a library entry point, so a bad target is the caller's
|
|
149
|
+
# error to surface. The CLI rescues this into the documented usage error (exit 1).
|
|
150
|
+
def self.validated_parser_target(parser_target)
|
|
151
|
+
return unless parser_target
|
|
152
|
+
|
|
153
|
+
Gem::Version.new(parser_target)
|
|
154
|
+
rescue ArgumentError
|
|
155
|
+
raise ArgumentError, "Invalid --parser-target '#{parser_target}'. Expected a version like 3.4 or 4.0."
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# An unusable exclusion regex is a usage error, like an invalid --parser-target: ignoring it
|
|
159
|
+
# would silently parse everything the caller meant to skip, so it fails with a message rather
|
|
160
|
+
# than a RegexpError backtrace. A missing value falls back to the documented default so the
|
|
161
|
+
# library API can be called without it.
|
|
162
|
+
def self.validated_exclude(pattern)
|
|
163
|
+
Regexp.new(pattern || CLI::DEFAULTS[:exclude])
|
|
164
|
+
rescue RegexpError, TypeError => e
|
|
165
|
+
raise ArgumentError, "Invalid --exclude '#{pattern}': #{e.message}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.validated_threads(value)
|
|
169
|
+
return CLI::DEFAULT_THREADS unless value
|
|
170
|
+
|
|
171
|
+
count = Integer(value.to_s, 10)
|
|
172
|
+
return count if count >= 1
|
|
173
|
+
|
|
174
|
+
raise ArgumentError
|
|
175
|
+
rescue ArgumentError, TypeError
|
|
176
|
+
Logger.warn "Invalid --threads '#{value}'; using the default of #{CLI::DEFAULT_THREADS}."
|
|
177
|
+
CLI::DEFAULT_THREADS
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# A bad depth is a configuration mistake, not a usage error: warn and fall back to the
|
|
181
|
+
# default rather than failing the run (the same policy as an unknown --log level).
|
|
182
|
+
def self.validated_max_depth(value)
|
|
183
|
+
return NodeHandling::MAX_NESTING_DEPTH unless value
|
|
184
|
+
|
|
185
|
+
depth = Integer(value.to_s, 10)
|
|
186
|
+
return depth if depth >= 1
|
|
187
|
+
|
|
188
|
+
raise ArgumentError
|
|
189
|
+
rescue ArgumentError, TypeError
|
|
190
|
+
Logger.warn "Invalid --max-depth '#{value}'; using the default of #{NodeHandling::MAX_NESTING_DEPTH}."
|
|
191
|
+
NodeHandling::MAX_NESTING_DEPTH
|
|
192
|
+
end
|
|
193
|
+
# Process a single file and generate its AST. +state+ is the per-run aggregate (see .parse);
|
|
194
|
+
# every outcome is recorded there so the manifest can report what a run actually did.
|
|
195
|
+
def self.process_file(file_path, output_dir, exclude_regex, base_dir, parser_target: nil,
|
|
196
|
+
max_depth: NodeHandling::MAX_NESTING_DEPTH, state: nil)
|
|
197
|
+
# Get the relative path of the file to apply exclusion rules
|
|
198
|
+
relative_path = file_path.sub(%r{^.*\/}, '')
|
|
199
|
+
# In single-file mode the caller passes the file itself as base_dir, so the sub below would be
|
|
200
|
+
# a no-op and both the exclusion match and rel_file_path would see the *absolute* path: `-i
|
|
201
|
+
# /tmp/x/keep.rb -e tmp` dropped the file because "tmp" appears in a directory component
|
|
202
|
+
# (plan 03 §5). Only the path relative to the input is ever matched or reported.
|
|
203
|
+
relative_input_path = if base_dir == file_path
|
|
204
|
+
File.basename(file_path)
|
|
205
|
+
else
|
|
206
|
+
file_path.sub("#{base_dir}/", '')
|
|
207
|
+
end
|
|
208
|
+
# Skip if the file matches the exclusion regex
|
|
209
|
+
if exclude_regex && exclude_regex.match?(relative_input_path)
|
|
210
|
+
RubyAstGen::Logger.debug "Excluding: #{relative_input_path}"
|
|
211
|
+
# Directory runs filter at scan time, so this only fires (and counts) for single-file inputs.
|
|
212
|
+
count_state(state, :excluded)
|
|
213
|
+
return
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
unless ruby_file?(file_path) # Skip if it's not a Ruby-related file
|
|
217
|
+
count_state(state, :skipped_nonruby)
|
|
218
|
+
return
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
file_result = {outcome: :empty, parse_error: nil}
|
|
222
|
+
begin
|
|
223
|
+
ast = parse_file(file_path, relative_input_path, parser_target: parser_target,
|
|
224
|
+
max_depth: max_depth, result: file_result)
|
|
225
|
+
if ast.nil?
|
|
226
|
+
# A failed parse carries its diagnosis in file_result; a nil AST from a clean parse is a
|
|
227
|
+
# comments-only file, which parsed fine but produces no JSON (counted as parsed-empty).
|
|
228
|
+
record_failure(state, file_path, relative_input_path, file_result[:parse_error]) if file_result[:outcome] == :failed
|
|
229
|
+
count_state(state, :parsed) unless file_result[:outcome] == :failed
|
|
230
|
+
return
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
output_path = File.join(output_dir, "#{relative_path}.json")
|
|
234
|
+
|
|
235
|
+
File.write(output_path, serialize(ast, max_depth))
|
|
236
|
+
count_state(state, :parsed)
|
|
237
|
+
count_state(state, :truncated_files) if ast[:truncated_nodes] > 0
|
|
238
|
+
rescue *PER_FILE_ERRORS => e
|
|
239
|
+
RubyAstGen::Logger.info "'#{relative_input_path}' - #{describe_error(e)}"
|
|
240
|
+
# Not a syntax error but the file still produced nothing: report it with the error class as
|
|
241
|
+
# the reason, since there is no parser diagnostic to point at.
|
|
242
|
+
record_failure(state, file_path, relative_input_path,
|
|
243
|
+
{message: describe_error(e), line: nil, column: nil, diagnostic_reason: e.class.to_s})
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# The stdlib generator, with a depth-safe fallback.
|
|
248
|
+
#
|
|
249
|
+
# max_nesting must exceed what a tree truncated at max_depth can reach: the default of 100 fails
|
|
250
|
+
# at an AST depth of ~49 and the resulting JSON::NestingError used to drop the whole file (logged
|
|
251
|
+
# as a per-file problem, exit 0) even though the AST itself was fine.
|
|
252
|
+
#
|
|
253
|
+
# How deep JSON.generate can go is a property of the runtime rather than of the JSON: CRuby's C
|
|
254
|
+
# extension manages ~100_000 levels, but on TruffleRuby it runs through Sulong and a tree at the
|
|
255
|
+
# default cap of 250 AST levels exhausts a worker thread's stack — which cost the whole file,
|
|
256
|
+
# the one outcome the depth cap exists to prevent, and made the output depend on the interpreter.
|
|
257
|
+
# Rather than guess each runtime's ceiling, fall back to the iterative writer when the recursive
|
|
258
|
+
# one runs out of room: it is ~11x slower on a corpus of real files, so it earns its keep only
|
|
259
|
+
# for the pathological ones. NodeHandling.dump_json is byte-identical to JSON.generate.
|
|
260
|
+
def self.serialize(ast, max_depth)
|
|
261
|
+
JSON.generate(ast, max_nesting: NodeHandling.json_nesting_limit(max_depth))
|
|
262
|
+
rescue SystemStackError, JSON::NestingError
|
|
263
|
+
NodeHandling.dump_json(ast)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Java errors arrive with an empty message (JRuby's java.lang.StackOverflowError has none), so
|
|
267
|
+
# the class name is the only thing that would tell the reader what happened.
|
|
268
|
+
def self.describe_error(error)
|
|
269
|
+
message = error.message.to_s
|
|
270
|
+
message.empty? ? error.class.to_s : message
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def self.process_directory(dir_path, output_dir, exclude_regex, max_threads = CLI::DEFAULT_THREADS,
|
|
274
|
+
parser_target: nil, max_depth: NodeHandling::MAX_NESTING_DEPTH, state: nil)
|
|
275
|
+
threads = []
|
|
276
|
+
queue = Queue.new
|
|
277
|
+
|
|
278
|
+
# FNM_DOTMATCH: without it Dir.glob skips dotfiles and dot-directories entirely, so `.ci/*.rb`
|
|
279
|
+
# and `.irbrc` were never parsed (plan 03 §5). SKIPPED_DIRECTORIES keeps that from pulling in
|
|
280
|
+
# tool caches — a repo with `bundle config path .bundle` would otherwise hand us every
|
|
281
|
+
# vendored gem, which the default exclusion regex does not cover.
|
|
282
|
+
Dir.glob("#{dir_path}/**/*", File::FNM_DOTMATCH).each do |path|
|
|
283
|
+
next unless File.file?(path)
|
|
284
|
+
relative_dir = path.sub("#{dir_path}/", '')
|
|
285
|
+
if skipped_directory?(relative_dir)
|
|
286
|
+
count_state(state, :excluded)
|
|
287
|
+
next
|
|
288
|
+
end
|
|
289
|
+
unless ruby_file?(path)
|
|
290
|
+
count_state(state, :skipped_nonruby)
|
|
291
|
+
next
|
|
292
|
+
end
|
|
293
|
+
if exclude_regex.match?(relative_dir)
|
|
294
|
+
count_state(state, :excluded)
|
|
295
|
+
next
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
queue << path
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
max_threads.times do
|
|
302
|
+
threads << Thread.new do
|
|
303
|
+
until queue.empty?
|
|
304
|
+
begin
|
|
305
|
+
path = queue.pop(true) rescue nil # Non-blocking pop
|
|
306
|
+
next unless path
|
|
307
|
+
|
|
308
|
+
relative_path = path.sub(dir_path, '')
|
|
309
|
+
output_subdir = File.join(output_dir, File.dirname(relative_path))
|
|
310
|
+
FileUtils.mkdir_p(output_subdir)
|
|
311
|
+
|
|
312
|
+
process_file(path, output_subdir, exclude_regex, dir_path, parser_target: parser_target,
|
|
313
|
+
max_depth: max_depth, state: state)
|
|
314
|
+
rescue *PER_FILE_ERRORS => e
|
|
315
|
+
# Raised here rather than inside process_file's own rescue, this would kill the worker
|
|
316
|
+
# and silently abandon the rest of its queue — join does not re-raise (plan 03 §7).
|
|
317
|
+
RubyAstGen::Logger.info "Error processing #{path}: #{describe_error(e)}"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
threads.each(&:join)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# All state mutations from worker threads funnel through here (and .record_failure), guarded by
|
|
327
|
+
# the run's mutex.
|
|
328
|
+
def self.count_state(state, key)
|
|
329
|
+
return unless state
|
|
330
|
+
|
|
331
|
+
state[:mutex].synchronize { state[key] += 1 }
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def self.record_failure(state, file_path, relative_input_path, parse_error)
|
|
335
|
+
return unless state
|
|
336
|
+
|
|
337
|
+
state[:mutex].synchronize do
|
|
338
|
+
state[:failed] += 1
|
|
339
|
+
state[:parse_errors] << {file_path: file_path, rel_file_path: relative_input_path,
|
|
340
|
+
parse_error: parse_error}
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# The run manifest (plan 03 §4): one JSON object naming what this run saw and what it did, so a
|
|
345
|
+
# consumer can tell "parsed nothing" apart from "was never run".
|
|
346
|
+
def self.manifest_for(input_path, output_dir, state, threads:, max_depth:, parser_target:)
|
|
347
|
+
{
|
|
348
|
+
input: input_path,
|
|
349
|
+
output: output_dir,
|
|
350
|
+
ruby_version: RUBY_VERSION,
|
|
351
|
+
# The *selected* backend, so --parser-target is reflected here rather than the newest
|
|
352
|
+
# grammar available. A file whose retry swapped the grammar records its own backend in its
|
|
353
|
+
# own JSON; this key describes the run's configuration.
|
|
354
|
+
parser_backend: parser_for_current_ruby(log: false, parser_target: parser_target).to_s,
|
|
355
|
+
generator_version: VERSION,
|
|
356
|
+
generated_at: Time.now.utc.iso8601,
|
|
357
|
+
files_parsed: state[:parsed],
|
|
358
|
+
files_failed: state[:failed],
|
|
359
|
+
files_skipped_nonruby: state[:skipped_nonruby],
|
|
360
|
+
files_excluded: state[:excluded],
|
|
361
|
+
truncated_files: state[:truncated_files],
|
|
362
|
+
threads: threads,
|
|
363
|
+
max_depth: max_depth,
|
|
364
|
+
parser_target: parser_target&.to_s
|
|
365
|
+
}
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Writes the two side-records into the output directory: the per-failure diagnostics JSONL (only
|
|
369
|
+
# when at least one file failed — a stale file from a previous run into the same directory is
|
|
370
|
+
# removed, so the record always describes *this* run) and the manifest. A write failure here is
|
|
371
|
+
# logged and otherwise ignored: the AST output is already on disk, and losing the summary must
|
|
372
|
+
# not turn a finished run into a failed one. PER_FILE_ERRORS rather than StandardError because
|
|
373
|
+
# serialization here runs out of stack the same way per-file serialization can.
|
|
374
|
+
def self.write_run_records(output_dir, state, manifest)
|
|
375
|
+
diagnostics_path = File.join(output_dir, DIAGNOSTICS_FILENAME)
|
|
376
|
+
if state[:parse_errors].any?
|
|
377
|
+
File.write(diagnostics_path, state[:parse_errors].map { |record| JSON.generate(record) }.join("\n") + "\n")
|
|
378
|
+
elsif File.exist?(diagnostics_path)
|
|
379
|
+
File.delete(diagnostics_path)
|
|
380
|
+
end
|
|
381
|
+
File.write(File.join(output_dir, MANIFEST_FILENAME), JSON.generate(manifest) + "\n")
|
|
382
|
+
rescue *PER_FILE_ERRORS => e
|
|
383
|
+
Logger.error "Failed to write #{DIAGNOSTICS_FILENAME}/#{MANIFEST_FILENAME}: #{describe_error(e)}"
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
PARSER_CACHE_MUTEX = Mutex.new
|
|
387
|
+
private_constant :PARSER_CACHE_MUTEX
|
|
388
|
+
|
|
389
|
+
# Resolves the parser class for a run. Parsing does not depend on the running Ruby VM, so the
|
|
390
|
+
# grammar is picked by capability rather than by RUBY_VERSION: the newest grammar available by
|
|
391
|
+
# default, or the one matching +parser_target+ when a target is requested. Memoized per target
|
|
392
|
+
# because worker threads resolve this once per file.
|
|
393
|
+
def self.parser_for_current_ruby(log: true, parser_target: nil)
|
|
394
|
+
require 'parser/source/buffer'
|
|
395
|
+
|
|
396
|
+
key = parser_target.nil? ? :newest : version_pair(parser_target)
|
|
397
|
+
PARSER_CACHE_MUTEX.synchronize do
|
|
398
|
+
cached = parser_cache[key]
|
|
399
|
+
next cached if cached
|
|
400
|
+
|
|
401
|
+
parser = key == :newest ? newest_available_parser : parser_for_target(key, log: log)
|
|
402
|
+
RubyAstGen::Logger.debug "Using parser: #{parser}" if log
|
|
403
|
+
parser_cache[key] = parser
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def self.parser_cache
|
|
408
|
+
@parser_cache ||= {}
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Drops the memoized parser selections; only needed by tests that flip capabilities.
|
|
412
|
+
def self.reset_parser_cache!
|
|
413
|
+
PARSER_CACHE_MUTEX.synchronize { @parser_cache = {} }
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Grammar selection for an explicit target, in order of fidelity:
|
|
417
|
+
# 1. the prism translation grammar for exactly that version
|
|
418
|
+
# 2. the `parser` gem grammar for exactly that version (covers 1.8 - 3.4, which prism's
|
|
419
|
+
# translation layer does not expose)
|
|
420
|
+
# 3. the closest older prism grammar
|
|
421
|
+
# 4. the newest grammar available
|
|
422
|
+
def self.parser_for_target(target, log: true)
|
|
423
|
+
exact_prism = prism_translation_parser_named("Parser#{target[0]}#{target[1]}")
|
|
424
|
+
return exact_prism if exact_prism
|
|
425
|
+
|
|
426
|
+
exact_parser_gem = parser_gem_grammar_for(target)
|
|
427
|
+
if exact_parser_gem
|
|
428
|
+
RubyAstGen::Logger.debug "No prism grammar for Ruby #{target.join('.')}; using #{exact_parser_gem}" if log
|
|
429
|
+
return exact_parser_gem
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
fallback = closest_prism_translation_parser(target) || newest_available_parser
|
|
433
|
+
RubyAstGen::Logger.info "No grammar for Ruby #{target.join('.')}; using #{fallback}" if log
|
|
434
|
+
fallback
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Newest grammar the installed gems can translate; the `parser` gem's runtime-version grammar
|
|
438
|
+
# is the last resort, for runtimes without prism.
|
|
439
|
+
def self.newest_available_parser
|
|
440
|
+
newest_prism_translation_parser || current_ruby_parser
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def self.current_ruby_parser
|
|
444
|
+
require 'parser/current'
|
|
445
|
+
::Parser::CurrentRuby
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def self.prism_available?
|
|
449
|
+
return @prism_available unless @prism_available.nil?
|
|
450
|
+
|
|
451
|
+
@prism_available =
|
|
452
|
+
begin
|
|
453
|
+
require 'prism'
|
|
454
|
+
require 'prism/translation'
|
|
455
|
+
true
|
|
456
|
+
rescue LoadError
|
|
457
|
+
false
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# The Ruby grammar level the selected parser implements, which is what decides whether a
|
|
462
|
+
# given syntax parses — not RUBY_VERSION.
|
|
463
|
+
def self.parser_grammar_version(parser = parser_for_current_ruby(log: false))
|
|
464
|
+
match = parser.to_s.match(/(\d)(\d+)\z/)
|
|
465
|
+
return Gem::Version.new(RUBY_VERSION) unless match
|
|
466
|
+
|
|
467
|
+
Gem::Version.new("#{match[1]}.#{match[2]}")
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# `Gem.loaded_specs` is empty when the libraries come from a `--standalone` bundle instead of
|
|
471
|
+
# being activated through RubyGems — which is how atom-parsetools ships this tool, so
|
|
472
|
+
# `--parser-info` used to report "unavailable" for the very gem it was parsing with. The loaded
|
|
473
|
+
# library's own VERSION constant is the fallback; nil (reported as "unavailable") now means the
|
|
474
|
+
# library really is not loaded.
|
|
475
|
+
def self.library_version(gem_name, version_constant)
|
|
476
|
+
spec_version = Gem.loaded_specs[gem_name]&.version&.to_s
|
|
477
|
+
return spec_version if spec_version
|
|
478
|
+
return nil unless Object.const_defined?(version_constant)
|
|
479
|
+
|
|
480
|
+
Object.const_get(version_constant).to_s
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def self.parser_info(parser_target: nil)
|
|
484
|
+
parser = parser_for_current_ruby(log: false, parser_target: parser_target)
|
|
485
|
+
|
|
486
|
+
{
|
|
487
|
+
ruby_engine: defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby",
|
|
488
|
+
ruby_version: RUBY_VERSION,
|
|
489
|
+
ruby_platform: RUBY_PLATFORM,
|
|
490
|
+
parser_target: parser_target&.to_s,
|
|
491
|
+
parser_backend: parser.to_s,
|
|
492
|
+
grammar_version: parser_grammar_version(parser).to_s,
|
|
493
|
+
parser_gem_version: library_version("parser", "Parser::VERSION"),
|
|
494
|
+
prism_gem_version: library_version("prism", "Prism::VERSION"),
|
|
495
|
+
prism_translation_parsers: prism_translation_parser_names
|
|
496
|
+
}
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def self.parser_info_text(parser_target: nil)
|
|
500
|
+
info = parser_info(parser_target: parser_target)
|
|
501
|
+
[
|
|
502
|
+
"Ruby engine: #{info[:ruby_engine]}",
|
|
503
|
+
"Ruby version: #{info[:ruby_version]}",
|
|
504
|
+
"Ruby platform: #{info[:ruby_platform]}",
|
|
505
|
+
"Parser target: #{info[:parser_target] || 'newest available'}",
|
|
506
|
+
"Parser backend: #{info[:parser_backend]}",
|
|
507
|
+
"Grammar version: #{info[:grammar_version]}",
|
|
508
|
+
"Parser gem: #{info[:parser_gem_version] || 'unavailable'}",
|
|
509
|
+
"Prism gem: #{info[:prism_gem_version] || 'unavailable'}",
|
|
510
|
+
"Prism translation parsers: #{info[:prism_translation_parsers].empty? ? 'none' : info[:prism_translation_parsers].join(', ')}"
|
|
511
|
+
].join("\n")
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# [major, minor] for anything version-ish; a missing minor is treated as 0 ("4" -> [4, 0]).
|
|
515
|
+
def self.version_pair(version)
|
|
516
|
+
version = Gem::Version.new(version.to_s) unless version.is_a?(Gem::Version)
|
|
517
|
+
[version.segments[0].to_i, (version.segments[1] || 0).to_i]
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def self.prism_translation_parser_named(name)
|
|
521
|
+
return nil unless prism_available?
|
|
522
|
+
return nil unless ::Prism::Translation.const_defined?(name, false)
|
|
523
|
+
|
|
524
|
+
::Prism::Translation.const_get(name, false)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def self.parser_gem_grammar_for(target)
|
|
528
|
+
require "parser/ruby#{target[0]}#{target[1]}"
|
|
529
|
+
::Parser.const_get("Ruby#{target[0]}#{target[1]}", false)
|
|
530
|
+
rescue LoadError, NameError
|
|
531
|
+
nil
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# Highest grammar that is not newer than the target, else nil.
|
|
535
|
+
def self.closest_prism_translation_parser(target)
|
|
536
|
+
candidates = prism_translation_parser_candidates
|
|
537
|
+
not_newer = candidates.select { |major, minor, _parser| ([major, minor] <=> target) <= 0 }
|
|
538
|
+
(not_newer.max_by { |major, minor, _parser| [major, minor] } ||
|
|
539
|
+
candidates.max_by { |major, minor, _parser| [major, minor] })&.last
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def self.prism_translation_parser_candidates
|
|
543
|
+
return [] unless prism_available?
|
|
544
|
+
|
|
545
|
+
::Prism::Translation.constants.filter_map do |const_name|
|
|
546
|
+
match = const_name.to_s.match(/\AParser(\d)(\d+)\z/)
|
|
547
|
+
next unless match
|
|
548
|
+
|
|
549
|
+
[match[1].to_i, match[2].to_i, ::Prism::Translation.const_get(const_name, false)]
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def self.newest_prism_translation_parser
|
|
554
|
+
prism_translation_parser_candidates.max_by { |major, minor, _parser| [major, minor] }&.last
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def self.prism_translation_parser_names
|
|
558
|
+
return [] unless prism_available?
|
|
559
|
+
|
|
560
|
+
::Prism::Translation.constants.grep(/\AParser\d+\z/).map(&:to_s).sort
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Parses one file into the JSON hash to emit. +result+, when given, receives the per-file
|
|
564
|
+
# outcome for the run manifest: :parsed (JSON emitted), :empty (clean parse of a comments-only
|
|
565
|
+
# file, which produces no JSON but did not fail) or :failed (+parse_error+ carrying the
|
|
566
|
+
# diagnosis that lands in the diagnostics JSONL).
|
|
567
|
+
def self.parse_file(file_path, relative_input_path, parser_target: nil, max_depth: NodeHandling::MAX_NESTING_DEPTH,
|
|
568
|
+
result: nil)
|
|
569
|
+
parser_class = parser_for_current_ruby(parser_target: parser_target)
|
|
570
|
+
buffer, source_scrubbed = read_source_buffer(file_path)
|
|
571
|
+
ast, comments, parser_class, diagnostics, syntax_error = parse_with_retry(parser_class, buffer, file_path)
|
|
572
|
+
if syntax_error
|
|
573
|
+
result&.merge!(outcome: :failed, parse_error: parse_error_record(syntax_error))
|
|
574
|
+
return nil
|
|
575
|
+
end
|
|
576
|
+
unless ast
|
|
577
|
+
result&.merge!(outcome: :empty)
|
|
578
|
+
return nil
|
|
579
|
+
end
|
|
580
|
+
result&.merge!(outcome: :parsed)
|
|
581
|
+
|
|
582
|
+
# The truncation count is per-file state threaded through the traversal: module state would
|
|
583
|
+
# race across the 10 worker threads and misattribute truncations between files.
|
|
584
|
+
state = {truncated: 0, first_type: nil}
|
|
585
|
+
json_ast = NodeHandling::ast_to_json(ast, buffer.source, file_path: relative_input_path,
|
|
586
|
+
max_depth: max_depth, state: state)
|
|
587
|
+
# Assigned before the UTF-8 walk so comment text from oddly-encoded sources is normalized
|
|
588
|
+
# like every other payload. The comments come from the same parse as the AST, so a scrubbed
|
|
589
|
+
# file's report matches what was actually parsed.
|
|
590
|
+
json_ast[:magic_comments] = magic_comments(comments, ast)
|
|
591
|
+
payloads_scrubbed = force_utf8_payloads!(json_ast)
|
|
592
|
+
json_ast[:file_path] = file_path
|
|
593
|
+
json_ast[:rel_file_path] = relative_input_path
|
|
594
|
+
json_ast[:generator_version] = VERSION
|
|
595
|
+
json_ast[:parser_backend] = parser_class.to_s
|
|
596
|
+
json_ast[:ruby_version] = RUBY_VERSION
|
|
597
|
+
json_ast[:truncated_nodes] = state[:truncated]
|
|
598
|
+
json_ast[:encoding_scrubbed] = true if source_scrubbed || payloads_scrubbed
|
|
599
|
+
# Prism's error-tolerant warnings as data (plan 03 §3). The key is omitted when the parse was
|
|
600
|
+
# silent, like call_operator and heredoc — absence means "nothing to report".
|
|
601
|
+
records, truncated = diagnostics_records(diagnostics)
|
|
602
|
+
if records.any?
|
|
603
|
+
json_ast[:diagnostics] = records
|
|
604
|
+
json_ast[:diagnostics_truncated] = true if truncated
|
|
605
|
+
end
|
|
606
|
+
if state[:truncated] > 0
|
|
607
|
+
# One warning per file: the count and the node types live in the JSON, so a line per
|
|
608
|
+
# truncated node is noise.
|
|
609
|
+
Logger.warn "#{relative_input_path}: truncated #{state[:truncated]} node(s) at max depth " \
|
|
610
|
+
"#{max_depth} (first: #{state[:first_type]})"
|
|
611
|
+
end
|
|
612
|
+
json_ast
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# Reads the file as bytes and lets a parser source buffer decode it the way Ruby does, so a
|
|
616
|
+
# `# coding:` magic comment (or BOM) is honoured instead of everything being force-read as
|
|
617
|
+
# UTF-8. Undecodable content is degraded rather than dropped, in three tiers:
|
|
618
|
+
#
|
|
619
|
+
# 1. decode faithfully (magic comment / BOM respected);
|
|
620
|
+
# 2. bytes invalid under the declared encoding (EncodingError) -> decode a scrubbed copy;
|
|
621
|
+
# 3. encoding name unusable (Parser::UnknownEncodingInMagicComment, an ArgumentError) ->
|
|
622
|
+
# skip detection altogether and take the scrubbed bytes as UTF-8.
|
|
623
|
+
#
|
|
624
|
+
# The second return value marks tiers 2 and 3 as degraded. I/O errors are not caught here:
|
|
625
|
+
# an unreadable file is the caller's problem to log, and swallowing them would report a
|
|
626
|
+
# missing file as an encoding problem.
|
|
627
|
+
def self.read_source_buffer(file_path)
|
|
628
|
+
raw = File.binread(file_path)
|
|
629
|
+
|
|
630
|
+
begin
|
|
631
|
+
[decoded_source_buffer(file_path, raw), false]
|
|
632
|
+
rescue EncodingError, ArgumentError
|
|
633
|
+
begin
|
|
634
|
+
[decoded_source_buffer(file_path, scrub_to_utf8(raw)), true]
|
|
635
|
+
rescue EncodingError, ArgumentError
|
|
636
|
+
[undecoded_source_buffer(file_path, scrub_to_utf8(raw)), true]
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Buffers built with +source=+ run the parser gem's encoding detection. The bytes are tagged
|
|
642
|
+
# UTF-8 first so that a file *without* a magic comment keeps the encoding (and therefore the
|
|
643
|
+
# character offsets) of the String this replaced; detection re-encodes from there when the
|
|
644
|
+
# file says otherwise.
|
|
645
|
+
def self.decoded_source_buffer(file_path, bytes)
|
|
646
|
+
Parser::Source::Buffer.new(file_path, source: bytes.dup.force_encoding(Encoding::UTF_8))
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# +raw_source=+ skips detection entirely, which is the only way to read a file whose declared
|
|
650
|
+
# encoding cannot be resolved. It still normalises CRLF, so offsets stay consistent.
|
|
651
|
+
def self.undecoded_source_buffer(file_path, utf8_source)
|
|
652
|
+
buffer = Parser::Source::Buffer.new(file_path)
|
|
653
|
+
buffer.raw_source = utf8_source
|
|
654
|
+
buffer
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def self.scrub_to_utf8(bytes)
|
|
658
|
+
bytes.dup.force_encoding(Encoding::UTF_8).scrub
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# Rewrites every String/Symbol payload in the emitted structure as valid UTF-8 so that
|
|
662
|
+
# JSON.generate cannot fail, and reports whether anything had to be replaced. Needed even
|
|
663
|
+
# when the source decoded cleanly: a `# coding: ASCII-8BIT` file is decoded faithfully (any
|
|
664
|
+
# byte is valid in that encoding) and yields binary node values and code snippets, which
|
|
665
|
+
# JSON.generate rejects. Top-level path keys are assigned after this walk, so file_path stays
|
|
666
|
+
# byte-for-byte what the caller passed in.
|
|
667
|
+
def self.force_utf8_payloads!(json_ast)
|
|
668
|
+
state = { scrubbed: false }
|
|
669
|
+
sanitize_utf8!(json_ast, state)
|
|
670
|
+
state[:scrubbed]
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Returns the sanitized value so containers can re-assign it. The scrub flag is accumulated
|
|
674
|
+
# in +state+ rather than folded out of the return values: combining them with `||=` would
|
|
675
|
+
# short-circuit the recursive call and silently skip whole subtrees once one string had been
|
|
676
|
+
# replaced (which shipped once, emitting JSON that still failed to serialize).
|
|
677
|
+
# Walks the built JSON with an explicit stack rather than by recursion. The tree is as deep as
|
|
678
|
+
# the AST (up to --max-depth), and recursion here cost ~2 frames per level: on runtimes with
|
|
679
|
+
# smaller thread stacks (JRuby, TruffleRuby, Windows CRuby, and any worker thread) a deeply
|
|
680
|
+
# nested file raised SystemStackError *after* the AST walk had already truncated correctly, so
|
|
681
|
+
# the file was skipped by process_file — the one outcome the depth cap exists to prevent.
|
|
682
|
+
# Iterating has no depth limit at all, so the scrub is no longer a reason to lose a file.
|
|
683
|
+
def self.sanitize_utf8!(object, state)
|
|
684
|
+
root = [object]
|
|
685
|
+
pending = [root]
|
|
686
|
+
|
|
687
|
+
until pending.empty?
|
|
688
|
+
container = pending.pop
|
|
689
|
+
case container
|
|
690
|
+
when Hash
|
|
691
|
+
container.each do |key, value|
|
|
692
|
+
replacement, descend = sanitize_payload(value, state)
|
|
693
|
+
container[key] = replacement
|
|
694
|
+
pending << replacement if descend
|
|
695
|
+
end
|
|
696
|
+
when Array
|
|
697
|
+
container.each_index do |index|
|
|
698
|
+
replacement, descend = sanitize_payload(container[index], state)
|
|
699
|
+
container[index] = replacement
|
|
700
|
+
pending << replacement if descend
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
root.first
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# Returns the value to store and whether it is a container the walk must descend into.
|
|
709
|
+
def self.sanitize_payload(value, state)
|
|
710
|
+
case value
|
|
711
|
+
when Hash, Array
|
|
712
|
+
[value, true]
|
|
713
|
+
when String
|
|
714
|
+
utf8, scrubbed = to_utf8(value)
|
|
715
|
+
state[:scrubbed] = true if scrubbed
|
|
716
|
+
[utf8, false]
|
|
717
|
+
when Symbol
|
|
718
|
+
utf8, scrubbed = to_utf8(value.to_s)
|
|
719
|
+
state[:scrubbed] = true if scrubbed
|
|
720
|
+
[scrubbed ? utf8.to_sym : value, false]
|
|
721
|
+
else
|
|
722
|
+
[value, false]
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def self.to_utf8(string)
|
|
727
|
+
return [string, false] if string.encoding == Encoding::UTF_8 && string.valid_encoding?
|
|
728
|
+
return [string.scrub, true] if string.encoding == Encoding::UTF_8
|
|
729
|
+
|
|
730
|
+
[string.encode(Encoding::UTF_8), false]
|
|
731
|
+
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
|
|
732
|
+
[string.encode(Encoding::UTF_8, invalid: :replace, undef: :replace), true]
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Returns the parsed AST, the raw comment list, the parser that produced them, that parse's
|
|
736
|
+
# non-fatal diagnostics, and the syntax error when the file could not be parsed — so callers
|
|
737
|
+
# can record the effective backend, derive magic comments from the parse that actually
|
|
738
|
+
# succeeded, and report failures as data. Comments are collected in the same lexing pass as
|
|
739
|
+
# the AST (parse_with_comments), so there is no second parse.
|
|
740
|
+
#
|
|
741
|
+
# Each attempt parses with a fresh parser (see .instantiate_parser) and its own diagnostics
|
|
742
|
+
# sink: the retry's warnings must not be mixed with the failed grammar's view of the file, so
|
|
743
|
+
# only the attempt that produced the returned AST contributes.
|
|
744
|
+
def self.parse_with_retry(parser_class, buffer, file_path)
|
|
745
|
+
parser = instantiate_parser(parser_class, diagnostics = [])
|
|
746
|
+
ast, comments = parser.parse_with_comments(buffer)
|
|
747
|
+
[ast, comments, parser_class, diagnostics, nil]
|
|
748
|
+
rescue ::Parser::SyntaxError => e
|
|
749
|
+
retry_class = syntax_retry_parser(parser_class)
|
|
750
|
+
if retry_class.nil?
|
|
751
|
+
RubyAstGen::Logger.info "Failed to parse #{file_path}: #{e.message}"
|
|
752
|
+
return [nil, [], parser_class, [], e]
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
RubyAstGen::Logger.info "Retrying #{file_path} with #{retry_class} after: #{e.message.lines.first&.strip}"
|
|
756
|
+
begin
|
|
757
|
+
parser = instantiate_parser(retry_class, diagnostics = [])
|
|
758
|
+
ast, comments = parser.parse_with_comments(buffer)
|
|
759
|
+
[ast, comments, retry_class, diagnostics, nil]
|
|
760
|
+
rescue ::Parser::SyntaxError => retry_error
|
|
761
|
+
RubyAstGen::Logger.info "Failed to parse #{file_path}: #{retry_error.message}"
|
|
762
|
+
[nil, [], retry_class, [], retry_error]
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# A fresh parser per parse call, with warnings routed into +sink+ instead of dropped. The
|
|
767
|
+
# memoized default_parser is shared by the worker threads, so installing a per-file consumer
|
|
768
|
+
# there would race one file's warnings into another's report. Error behaviour is unchanged:
|
|
769
|
+
# all_errors_are_fatal is what default_parser configures too. ignore_warnings=false is the one
|
|
770
|
+
# deliberate divergence — collecting warnings is the point (plan 03 §3).
|
|
771
|
+
def self.instantiate_parser(parser_class, sink)
|
|
772
|
+
parser = parser_class.new
|
|
773
|
+
parser.diagnostics.all_errors_are_fatal = true
|
|
774
|
+
parser.diagnostics.ignore_warnings = false
|
|
775
|
+
parser.diagnostics.consumer = ->(diagnostic) { sink << diagnostic }
|
|
776
|
+
parser
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# The syntax error as data for the diagnostics JSONL: the rendered message, where it sits in
|
|
780
|
+
# the file, and the parser's own reason symbol ("unexpected_token").
|
|
781
|
+
def self.parse_error_record(error)
|
|
782
|
+
diagnostic = error.diagnostic
|
|
783
|
+
location = diagnostic.location
|
|
784
|
+
{
|
|
785
|
+
message: diagnostic.message,
|
|
786
|
+
line: location.line,
|
|
787
|
+
column: location.column,
|
|
788
|
+
diagnostic_reason: diagnostic.reason.to_s
|
|
789
|
+
}
|
|
790
|
+
rescue NoMethodError
|
|
791
|
+
# A SyntaxError without a diagnostic is not something the parser gem emits; degrade to the
|
|
792
|
+
# message rather than losing the failure.
|
|
793
|
+
{message: describe_error(error), line: nil, column: nil, diagnostic_reason: nil}
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# Non-fatal diagnostics (prism warnings: ambiguous argument prefix, unused variable, ...) as
|
|
797
|
+
# data, capped at DIAGNOSTICS_CAP entries. The second return value reports whether the cap cut
|
|
798
|
+
# anything, so the file can carry diagnostics_truncated.
|
|
799
|
+
def self.diagnostics_records(diagnostics)
|
|
800
|
+
records = diagnostics.filter_map do |diagnostic|
|
|
801
|
+
location = diagnostic.location
|
|
802
|
+
{severity: diagnostic.level.to_s, message: diagnostic.message,
|
|
803
|
+
line: location&.line, column: location&.column}
|
|
804
|
+
end
|
|
805
|
+
truncated = records.size > DIAGNOSTICS_CAP
|
|
806
|
+
[truncated ? records.first(DIAGNOSTICS_CAP) : records, truncated]
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# Magic comments ("# key: value" comments) reported as data, in source order. The rule runs
|
|
810
|
+
# over the comment tokens of the parse that produced the AST: the whole comment must be the
|
|
811
|
+
# `key: value` pair (a trailing word makes it an ordinary comment), whitespace around the colon
|
|
812
|
+
# is optional, and the comment must appear in the file prologue — before the first line of
|
|
813
|
+
# code. Only `#` comments qualify: =begin/=end documents come back as type :document, never
|
|
814
|
+
# magic comments, on both backends. Double-quoted values keep only their content; single quotes
|
|
815
|
+
# are left as written.
|
|
816
|
+
#
|
|
817
|
+
# The prologue restriction is where this deliberately diverges from
|
|
818
|
+
# `Prism.parse(...).magic_comments`, which is a lexical scan with no position rule and
|
|
819
|
+
# therefore reports RDoc and commented-out code as magic comments (`# https://memcached.org`
|
|
820
|
+
# → `{https => //memcached.org}`, `# See: docs`, `# attr_reader: foo`). Ruby only honours these
|
|
821
|
+
# comments before any code — a `frozen_string_literal` after code is ignored with a warning,
|
|
822
|
+
# and Sorbet's `typed:` sigil must precede code as well — so the prologue is what "magic"
|
|
823
|
+
# means. Measured over 7 104 gem files: the restriction drops 1 472 of 7 094 reported entries
|
|
824
|
+
# while keeping every one of the 5 430 real magic comments (frozen_string_literal, coding,
|
|
825
|
+
# encoding, typed).
|
|
826
|
+
MAGIC_COMMENT_PATTERN = /\A#\s*([A-Za-z0-9_-]+)\s*:\s*(\S+)[ \t]*\z/.freeze
|
|
827
|
+
private_constant :MAGIC_COMMENT_PATTERN
|
|
828
|
+
|
|
829
|
+
def self.magic_comments(comments, ast = nil)
|
|
830
|
+
# Comments at or after the first byte of code are ordinary comments. When the root node has
|
|
831
|
+
# no location to compare against (never seen in practice), position is not enforced rather
|
|
832
|
+
# than dropping everything.
|
|
833
|
+
first_code = ast&.location&.expression&.begin_pos
|
|
834
|
+
|
|
835
|
+
comments.filter_map do |comment|
|
|
836
|
+
next unless comment.is_a?(::Parser::Source::Comment) && comment.type == :inline
|
|
837
|
+
next if first_code && comment.loc.expression.begin_pos >= first_code
|
|
838
|
+
|
|
839
|
+
match = comment.text.match(MAGIC_COMMENT_PATTERN)
|
|
840
|
+
next unless match
|
|
841
|
+
|
|
842
|
+
value = match[2]
|
|
843
|
+
# Double-quoted values keep only their content, matching how prism reports
|
|
844
|
+
# `# coding: "utf-8"`.
|
|
845
|
+
value = value[1..-2] if value.length >= 2 && value.start_with?('"') && value.end_with?('"')
|
|
846
|
+
{name: match[1], value: value, line: comment.loc.line}
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
# A file rejected by an older grammar may still be valid newer Ruby, so retry once with the
|
|
851
|
+
# newest grammar available. Skipped when that is the grammar that just failed.
|
|
852
|
+
def self.syntax_retry_parser(failed_parser)
|
|
853
|
+
newest = newest_prism_translation_parser
|
|
854
|
+
newest if newest && newest != failed_parser
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
# Files parsed as Ruby: well-known basenames of Ruby DSL files, in any directory, plus
|
|
858
|
+
# Ruby-ish extensions. Both are matched case-insensitively (rake itself accepts `rakefile`),
|
|
859
|
+
# but basename matching is exact rather than prefix-based, so "Gemfile" is parsed while
|
|
860
|
+
# "Gemfile.lock" is not.
|
|
861
|
+
RUBY_FILE_BASENAMES = %w[
|
|
862
|
+
Rakefile Gemfile Capfile Thorfile Guardfile Berksfile Podfile Vagrantfile
|
|
863
|
+
Steepfile Puppetfile Dangerfile Fastfile Appfile Pluginfile Matchfile Scanfile
|
|
864
|
+
Snapfile Gymfile Deliverfile Brewfile
|
|
865
|
+
.irbrc .pryrc .simplecov
|
|
866
|
+
].map(&:downcase).freeze
|
|
867
|
+
private_constant :RUBY_FILE_BASENAMES
|
|
868
|
+
|
|
869
|
+
# Dot-directories that hold tool state or vendored code rather than the project's own sources.
|
|
870
|
+
# Matched by exact path component, so a project directory named `.ci` is still scanned.
|
|
871
|
+
SKIPPED_DIRECTORIES = %w[.git .svn .hg .bundle .gem .cache .venv .tox .idea .vscode].freeze
|
|
872
|
+
private_constant :SKIPPED_DIRECTORIES
|
|
873
|
+
|
|
874
|
+
def self.skipped_directory?(relative_path)
|
|
875
|
+
relative_path.split(File::SEPARATOR).any? { |part| SKIPPED_DIRECTORIES.include?(part) }
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
RUBY_FILE_EXTENSIONS = %w[.rb .gemspec .rake .ru .rbi .thor .jbuilder .axlsx .rabl].freeze
|
|
879
|
+
private_constant :RUBY_FILE_EXTENSIONS
|
|
880
|
+
|
|
881
|
+
def self.ruby_file?(file_path)
|
|
882
|
+
basename = File.basename(file_path).downcase
|
|
883
|
+
return true if RUBY_FILE_BASENAMES.include?(basename)
|
|
884
|
+
|
|
885
|
+
RUBY_FILE_EXTENSIONS.include?(File.extname(basename))
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
end
|