gloss 0.0.5 → 0.1.3
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.
- checksums.yaml +4 -4
- data/.gitattributes +3 -0
- data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
- data/.github/workflows/{ruby.yml → ruby_specs.yml} +2 -2
- data/.github/workflows/self_build.yml +45 -0
- data/.gloss.yml +1 -0
- data/Gemfile.lock +3 -3
- data/README.md +35 -5
- data/Rakefile +1 -1
- data/exe/gloss +13 -2
- data/ext/gloss/Makefile +8 -19
- data/ext/gloss/lib/cr_ruby.cr +5 -4
- data/ext/gloss/src/cr_ast.cr +61 -77
- data/ext/gloss/src/gloss.cr +7 -3
- data/ext/gloss/src/rb_ast.cr +37 -36
- data/lib/gloss.rb +11 -7
- data/lib/gloss/cli.rb +61 -23
- data/lib/gloss/config.rb +3 -1
- data/lib/gloss/errors.rb +1 -1
- data/lib/gloss/initializer.rb +2 -1
- data/lib/gloss/logger.rb +29 -0
- data/lib/gloss/parser.rb +17 -2
- data/lib/gloss/prog_loader.rb +141 -0
- data/lib/gloss/scope.rb +1 -1
- data/lib/gloss/source.rb +1 -1
- data/lib/gloss/type_checker.rb +80 -32
- data/lib/gloss/utils.rb +44 -0
- data/lib/gloss/version.rb +4 -4
- data/lib/gloss/{builder.rb → visitor.rb} +93 -54
- data/lib/gloss/watcher.rb +41 -19
- data/lib/gloss/writer.rb +21 -10
- data/sig/core.rbs +2 -0
- data/sig/fast_blank.rbs +4 -0
- data/sig/{gloss.rbs → gls.rbs} +0 -0
- data/sig/optparse.rbs +6 -0
- data/sig/rubygems.rbs +9 -0
- data/sig/yaml.rbs +3 -0
- data/src/exe/gloss +19 -0
- data/src/lib/gloss.gl +25 -0
- data/src/lib/gloss/cli.gl +40 -14
- data/src/lib/gloss/config.gl +2 -2
- data/src/lib/gloss/initializer.gl +1 -1
- data/src/lib/gloss/logger.gl +21 -0
- data/src/lib/gloss/parser.gl +17 -5
- data/src/lib/gloss/prog_loader.gl +133 -0
- data/src/lib/gloss/scope.gl +0 -2
- data/src/lib/gloss/type_checker.gl +85 -39
- data/src/lib/gloss/utils.gl +38 -0
- data/src/lib/gloss/version.gl +1 -1
- data/src/lib/gloss/{builder.gl → visitor.gl} +80 -49
- data/src/lib/gloss/watcher.gl +42 -24
- data/src/lib/gloss/writer.gl +15 -13
- metadata +22 -7
data/lib/gloss/config.rb
CHANGED
@@ -8,7 +8,9 @@ require "yaml"
|
|
8
8
|
module Gloss
|
9
9
|
CONFIG_PATH = ".gloss.yml"
|
10
10
|
Config = OpenStruct.new(default_config: {:frozen_string_literals => true,
|
11
|
-
:src_dir => "src"
|
11
|
+
:src_dir => "src",
|
12
|
+
:entrypoint => nil,
|
13
|
+
:strict_require => false}.freeze)
|
12
14
|
user_config = (if File.exist?(CONFIG_PATH)
|
13
15
|
YAML.safe_load(File.read(CONFIG_PATH))
|
14
16
|
else
|
data/lib/gloss/errors.rb
CHANGED
data/lib/gloss/initializer.rb
CHANGED
data/lib/gloss/logger.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
|
+
##### See src/ to make changes
|
5
|
+
|
6
|
+
module Gloss
|
7
|
+
def self.logger()
|
8
|
+
(if @logger
|
9
|
+
@logger
|
10
|
+
else
|
11
|
+
env_log_level = ENV.fetch("LOG_LEVEL") { ||
|
12
|
+
"INFO" }
|
13
|
+
real_log_level = {"UNKNOWN" => Logger::UNKNOWN,
|
14
|
+
"FATAL" => Logger::FATAL,
|
15
|
+
"ERROR" => Logger::ERROR,
|
16
|
+
"WARN" => Logger::WARN,
|
17
|
+
"INFO" => Logger::INFO,
|
18
|
+
"DEBUG" => Logger::DEBUG,
|
19
|
+
"NIL" => nil,
|
20
|
+
nil => nil,
|
21
|
+
"" => nil}.fetch(env_log_level)
|
22
|
+
@logger = Logger.new((if real_log_level
|
23
|
+
STDOUT
|
24
|
+
else
|
25
|
+
IO::NULL
|
26
|
+
end))
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
end
|
data/lib/gloss/parser.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
4
|
##### See src/ to make changes
|
5
5
|
|
6
|
-
|
6
|
+
module Gloss
|
7
7
|
class Parser
|
8
8
|
def initialize(str)
|
9
9
|
@str = str
|
@@ -13,7 +13,22 @@
|
|
13
13
|
begin
|
14
14
|
JSON.parse(tree_json, symbolize_names: true)
|
15
15
|
rescue JSON::ParserError
|
16
|
-
|
16
|
+
error_message = tree_json
|
17
|
+
error_message.match(/.+\s:(\d+)$/)
|
18
|
+
(if $~.[](1)
|
19
|
+
line_number = $~.[](1)
|
20
|
+
.to_i
|
21
|
+
context = @str.lines
|
22
|
+
.[]((( line_number.-(2)
|
23
|
+
)..(line_number)))
|
24
|
+
.map
|
25
|
+
.with_index() { |line, index|
|
26
|
+
"#{index.-(1)
|
27
|
+
.+(line_number)}| #{line}" }
|
28
|
+
.join
|
29
|
+
error_message = "#{context.rstrip}\n\n#{error_message}\n"
|
30
|
+
end)
|
31
|
+
throw(:"error", error_message)
|
17
32
|
end
|
18
33
|
end
|
19
34
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
|
+
##### See src/ to make changes
|
5
|
+
|
6
|
+
require "rubygems/gem_runner"
|
7
|
+
module Gloss
|
8
|
+
OUTPUT_BY_PATH = Hash.new
|
9
|
+
class ProgLoader
|
10
|
+
def initialize()
|
11
|
+
entrypoint = Config.entrypoint
|
12
|
+
(if entrypoint.==(nil) || entrypoint.==("")
|
13
|
+
throw(:"error", "Entrypoint is not yet set in .gloss.yml")
|
14
|
+
end)
|
15
|
+
@files_to_process = [Utils.absolute_path(Config.entrypoint), Utils.absolute_path(File.join(__dir__ || "", "..", "..", "sig", "core.rbs"))]
|
16
|
+
@processed_files = Set.new
|
17
|
+
@type_checker = TypeChecker.new
|
18
|
+
end
|
19
|
+
def run()
|
20
|
+
@files_to_process.each() { |path_string|
|
21
|
+
unless @processed_files.member?(path_string) || OUTPUT_BY_PATH.[](path_string)
|
22
|
+
Gloss.logger
|
23
|
+
.debug("Loading #{path_string}")
|
24
|
+
path = Utils.absolute_path(path_string)
|
25
|
+
file_contents = File.open(path)
|
26
|
+
.read
|
27
|
+
contents_tree = Parser.new(file_contents)
|
28
|
+
.run
|
29
|
+
on_new_file_referenced = proc() { |pa, relative|
|
30
|
+
(if relative
|
31
|
+
handle_require_relative(pa)
|
32
|
+
else
|
33
|
+
handle_require(pa)
|
34
|
+
end)
|
35
|
+
}
|
36
|
+
OUTPUT_BY_PATH.[]=(path_string, Visitor.new(contents_tree, @type_checker, on_new_file_referenced)
|
37
|
+
.run)
|
38
|
+
@processed_files.add(path_string)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
@type_checker
|
42
|
+
end
|
43
|
+
STDLIB_TYPE_DEPENDENCIES = {"yaml" => ["pstore", "dbm"],
|
44
|
+
"rbs" => ["logger", "set", "tsort"],
|
45
|
+
"logger" => ["monitor"]}
|
46
|
+
private def handle_require(path)
|
47
|
+
(if path.start_with?(".")
|
48
|
+
base = File.join(Dir.pwd, path)
|
49
|
+
fp = base.+(".gl")
|
50
|
+
(if File.exist?(fp)
|
51
|
+
@files_to_process.<<(fp)
|
52
|
+
end)
|
53
|
+
return
|
54
|
+
end)
|
55
|
+
full = File.absolute_path("#{File.join(Config.src_dir, "lib", path)}.gl")
|
56
|
+
pathn = Pathname.new(full)
|
57
|
+
(if pathn.file?
|
58
|
+
@files_to_process.<<(pathn.to_s)
|
59
|
+
else
|
60
|
+
pathn = Pathname.new("#{File.join(Dir.pwd, "sig", path)}.rbs")
|
61
|
+
gem_path = Utils.gem_path_for(path)
|
62
|
+
(if gem_path
|
63
|
+
sig_files = Dir.glob(File.absolute_path(File.join(gem_path, "..", "..", "sig", "**", "*.rbs")))
|
64
|
+
(if sig_files.length
|
65
|
+
.positive?
|
66
|
+
sig_files.each() { |fp|
|
67
|
+
@type_checker.load_sig_path(fp)
|
68
|
+
}
|
69
|
+
@processed_files.add(path)
|
70
|
+
rbs_type_deps = STDLIB_TYPE_DEPENDENCIES.fetch(path) { ||
|
71
|
+
nil }
|
72
|
+
(if rbs_type_deps
|
73
|
+
rbs_type_deps.each() { |d|
|
74
|
+
handle_require(d)
|
75
|
+
}
|
76
|
+
end)
|
77
|
+
return
|
78
|
+
end)
|
79
|
+
end)
|
80
|
+
(if pathn.file?
|
81
|
+
@type_checker.load_sig_path(pathn.to_s)
|
82
|
+
@processed_files.add(pathn.to_s)
|
83
|
+
else
|
84
|
+
rbs_stdlib_dir = File.absolute_path(File.join(@type_checker.rbs_gem_dir, "..", "..", "stdlib", path))
|
85
|
+
(if Pathname.new(rbs_stdlib_dir)
|
86
|
+
.exist?
|
87
|
+
load_rbs_from_require_path(path)
|
88
|
+
rbs_type_deps = STDLIB_TYPE_DEPENDENCIES.fetch(path) { ||
|
89
|
+
nil }
|
90
|
+
(if rbs_type_deps
|
91
|
+
rbs_type_deps.each() { |d|
|
92
|
+
load_rbs_from_require_path(d)
|
93
|
+
}
|
94
|
+
end)
|
95
|
+
else
|
96
|
+
(if Config.strict_require
|
97
|
+
throw(:"error", "Cannot resolve require path for #{path}")
|
98
|
+
else
|
99
|
+
Gloss.logger
|
100
|
+
.debug("No path found for #{path}")
|
101
|
+
end)
|
102
|
+
end)
|
103
|
+
end)
|
104
|
+
end)
|
105
|
+
end
|
106
|
+
private def handle_require_relative(path)
|
107
|
+
base = File.join(@filepath, "..", path)
|
108
|
+
# @type var pn: String?
|
109
|
+
pn = nil
|
110
|
+
Gem.suffixes
|
111
|
+
.each() { |ext|
|
112
|
+
full = File.absolute_path(base.+(ext))
|
113
|
+
(if File.exist?(full)
|
114
|
+
pn = full
|
115
|
+
end)
|
116
|
+
}
|
117
|
+
(if pn
|
118
|
+
unless @files_to_process.include?(pn)
|
119
|
+
@files_to_process.<<(pn)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
(if Config.strict_require
|
123
|
+
throw(:"error", "Cannot resolve require path for #{pn}")
|
124
|
+
else
|
125
|
+
Gloss.logger
|
126
|
+
.debug("No path found for #{path}")
|
127
|
+
end)
|
128
|
+
end)
|
129
|
+
end
|
130
|
+
private def rbs_stdlib_path_for(libr)
|
131
|
+
File.absolute_path(File.join(@type_checker.rbs_gem_dir, "..", "..", "stdlib", libr))
|
132
|
+
end
|
133
|
+
private def load_rbs_from_require_path(path)
|
134
|
+
Dir.glob(File.join(rbs_stdlib_path_for(path), "**", "*.rbs"))
|
135
|
+
.each() { |fp|
|
136
|
+
@type_checker.load_sig_path(fp)
|
137
|
+
@processed_files.add(fp)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/gloss/scope.rb
CHANGED
data/lib/gloss/source.rb
CHANGED
data/lib/gloss/type_checker.rb
CHANGED
@@ -3,64 +3,112 @@
|
|
3
3
|
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
4
|
##### See src/ to make changes
|
5
5
|
|
6
|
-
|
6
|
+
require "set"
|
7
|
+
module Gloss
|
7
8
|
class TypeChecker
|
8
9
|
Project = Struct.new(:"targets")
|
9
|
-
attr_reader(:"steep_target", :"top_level_decls")
|
10
|
+
attr_reader(:"steep_target", :"top_level_decls", :"env", :"rbs_gem_dir")
|
10
11
|
def initialize()
|
11
12
|
@steep_target = Steep::Project::Target.new(name: "gloss", options: Steep::Project::Options.new
|
12
13
|
.tap() { |o|
|
13
14
|
o.allow_unknown_constant_assignment=(true)
|
14
|
-
}, source_patterns: ["
|
15
|
-
@top_level_decls =
|
15
|
+
}, source_patterns: ["**/*.rb"], ignore_patterns: Array.new, signature_patterns: ["sig"])
|
16
|
+
@top_level_decls = Set.new
|
17
|
+
@rbs_gem_dir = Utils.gem_path_for("rbs")
|
18
|
+
env_loader = RBS::EnvironmentLoader.new
|
19
|
+
@env = RBS::Environment.from_loader(env_loader)
|
20
|
+
project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir)
|
21
|
+
.realpath)
|
22
|
+
project.targets
|
23
|
+
.<<(@steep_target)
|
24
|
+
loader = Steep::Project::FileLoader.new(project: project)
|
16
25
|
end
|
17
|
-
def run(rb_str)
|
18
|
-
|
19
|
-
|
26
|
+
def run(filepath, rb_str)
|
27
|
+
begin
|
28
|
+
valid_types = check_types(filepath, rb_str)
|
29
|
+
rescue ParseError => e
|
30
|
+
throw(:"error", "")
|
31
|
+
rescue => e
|
32
|
+
throw(:"error", "Type checking Error: #{e.message} (#{e.class})")
|
33
|
+
end
|
34
|
+
unless valid_types
|
35
|
+
errors = @steep_target.errors
|
20
36
|
.map() { |e|
|
21
37
|
case e
|
22
|
-
when Steep::
|
38
|
+
when Steep::Diagnostic::Ruby::NoMethod
|
23
39
|
"Unknown method :#{e.method}, location: #{e.type
|
24
40
|
.location
|
25
41
|
.inspect}"
|
26
|
-
when Steep::
|
42
|
+
when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
|
27
43
|
"Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
|
28
|
-
when Steep::
|
29
|
-
"
|
30
|
-
.
|
31
|
-
|
32
|
-
.method_name}"
|
33
|
-
when Steep::Errors::ReturnTypeMismatch
|
44
|
+
when Steep::Diagnostic::Ruby::IncompatibleArguments
|
45
|
+
"Invalid argmuents - method type: #{e.method_types
|
46
|
+
.first}\nmethod name: #{e.method_name}"
|
47
|
+
when Steep::Diagnostic::Ruby::ReturnTypeMismatch
|
34
48
|
"Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
|
35
|
-
when Steep::
|
49
|
+
when Steep::Diagnostic::Ruby::IncompatibleAssignment
|
36
50
|
"Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
|
51
|
+
when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
|
52
|
+
"Unexpected block given"
|
37
53
|
else
|
38
|
-
e.inspect
|
54
|
+
"#{e.header_line}\n#{e.inspect}"
|
39
55
|
end
|
40
56
|
}
|
41
|
-
.join("\n")
|
57
|
+
.join("\n")
|
58
|
+
throw(:"error", errors)
|
42
59
|
end
|
43
60
|
true
|
44
61
|
end
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir)
|
49
|
-
.realpath)
|
50
|
-
project.targets
|
51
|
-
.<<(@steep_target)
|
52
|
-
loader = Steep::Project::FileLoader.new(project: project)
|
53
|
-
loader.load_signatures
|
54
|
-
@steep_target.add_source("gloss.rb", rb_str)
|
55
|
-
@top_level_decls.each() { |_, decl|
|
56
|
-
env.<<(decl)
|
62
|
+
def ready_for_checking!()
|
63
|
+
@top_level_decls.each() { |decl|
|
64
|
+
@env.<<(decl)
|
57
65
|
}
|
58
|
-
env = env.resolve_type_names
|
59
|
-
@steep_target.instance_variable_set("@environment", env)
|
66
|
+
@env = @env.resolve_type_names
|
67
|
+
@steep_target.instance_variable_set("@environment", @env)
|
68
|
+
end
|
69
|
+
def check_types(filepath, rb_str)
|
70
|
+
@steep_target.add_source(filepath, rb_str)
|
71
|
+
ready_for_checking!
|
60
72
|
@steep_target.type_check
|
73
|
+
(if @steep_target.status
|
74
|
+
.is_a?(Steep::Project::Target::SignatureErrorStatus)
|
75
|
+
throw(:"error", @steep_target.status
|
76
|
+
.errors
|
77
|
+
.map() { |e|
|
78
|
+
msg = case e
|
79
|
+
when Steep::Diagnostic::Signature::UnknownTypeName
|
80
|
+
"Unknown type name: #{e.name
|
81
|
+
.name} (#{e.location
|
82
|
+
.source
|
83
|
+
.[](/^.*$/)})"
|
84
|
+
when Steep::Diagnostic::Signature::InvalidTypeApplication
|
85
|
+
"Invalid type application: #{e.header_line}"
|
86
|
+
when Steep::Diagnostic::Signature::DuplicatedMethodDefinition
|
87
|
+
"Duplicated method: #{e.header_line}"
|
88
|
+
else
|
89
|
+
e.header_line
|
90
|
+
end
|
91
|
+
" SignatureSyntaxError:\n Location: #{e.location}\n Message: \"#{msg}\"" }
|
92
|
+
.join("\n"))
|
93
|
+
end)
|
94
|
+
@steep_target.source_files
|
95
|
+
.each() { |path, f|
|
96
|
+
(if f.status
|
97
|
+
.is_a?(Steep::Project::SourceFile::ParseErrorStatus)
|
98
|
+
e = f.status
|
99
|
+
.error
|
100
|
+
throw(:"error", "#{e.class}: #{e.message}")
|
101
|
+
end)
|
102
|
+
}
|
61
103
|
@steep_target.status
|
62
104
|
.is_a?(Steep::Project::Target::TypeCheckStatus) && @steep_target.no_error? && @steep_target.errors
|
63
105
|
.empty?
|
64
106
|
end
|
107
|
+
def load_sig_path(path)
|
108
|
+
Gloss.logger
|
109
|
+
.debug("Loading signature file for #{path}")
|
110
|
+
@steep_target.add_signature(path, File.open(path)
|
111
|
+
.read)
|
112
|
+
end
|
65
113
|
end
|
66
114
|
end
|
data/lib/gloss/utils.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##### This file was generated by Gloss; any changes made here will be overwritten.
|
4
|
+
##### See src/ to make changes
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
module Gloss
|
8
|
+
module Utils
|
9
|
+
module_function
|
10
|
+
def absolute_path(path)
|
11
|
+
pn = Pathname.new(path)
|
12
|
+
(if pn.absolute?
|
13
|
+
pn.to_s
|
14
|
+
else
|
15
|
+
ap = File.absolute_path(path)
|
16
|
+
(if File.exist?(ap)
|
17
|
+
ap
|
18
|
+
else
|
19
|
+
throw(:"error", "File path #{path} does not exist (also looked for #{ap})")
|
20
|
+
end)
|
21
|
+
end)
|
22
|
+
end
|
23
|
+
def gem_path_for(gem_name)
|
24
|
+
begin
|
25
|
+
Gem.ui
|
26
|
+
.instance_variable_set(:"@outs", StringIO.new)
|
27
|
+
Gem::GemRunner.new
|
28
|
+
.run(["which", gem_name])
|
29
|
+
Gem.ui
|
30
|
+
.outs
|
31
|
+
.string
|
32
|
+
rescue SystemExit => e
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
def with_file_header(str)
|
37
|
+
"#{Visitor::FILE_HEADER}\n\n#{str}"
|
38
|
+
end
|
39
|
+
def src_path_to_output_path(src_path)
|
40
|
+
src_path.sub("#{Config.src_dir}/", "")
|
41
|
+
.sub(/\.gl$/, ".rb")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|