Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add crystal tool dependencies #13631

Merged
merged 7 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion etc/completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ _crystal()
_crystal_compgen_options "${opts}" "${cur}"
else
if [[ "${prev}" == "tool" ]] ; then
local subcommands="context format hierarchy implementations types"
local subcommands="context dependencies format hierarchy implementations types"
_crystal_compgen_options "${subcommands}" "${cur}"
else
_crystal_compgen_sources "${cur}"
Expand Down
12 changes: 12 additions & 0 deletions etc/completion.fish
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ complete -c crystal -n "__fish_seen_subcommand_from context" -s p -l progress -d
complete -c crystal -n "__fish_seen_subcommand_from context" -s t -l time -d "Enable execution time output"
complete -c crystal -n "__fish_seen_subcommand_from context" -l stdin-filename -d "Source file name to be read from STDIN"

complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "dependencies" -d "show tree of required source files" -x
complete -c crystal -n "__fish_seen_subcommand_from context" -s i -l include -d "Include path in output"
complete -c crystal -n "__fish_seen_subcommand_from context" -s e -l exclude -d "Exclude path in output"
complete -c crystal -n "__fish_seen_subcommand_from context" -s D -l define -d "Define a compile-time flag"
complete -c crystal -n "__fish_seen_subcommand_from context" -s f -l format -d "Output format 'tree' (default), 'flat', 'dot', or 'mermaid'." -a "tree flat dot mermaid" -f
complete -c crystal -n "__fish_seen_subcommand_from context" -l error-trace -d "Show full error trace"
complete -c crystal -n "__fish_seen_subcommand_from context" -l no-color -d "Disable colored output"
complete -c crystal -n "__fish_seen_subcommand_from context" -l prelude -d "Use given file as prelude"
complete -c crystal -n "__fish_seen_subcommand_from context" -s s -l stats -d "Enable statistics output"
complete -c crystal -n "__fish_seen_subcommand_from context" -s p -l progress -d "Enable progress output"
complete -c crystal -n "__fish_seen_subcommand_from context" -s t -l time -d "Enable execution time output"

complete -c crystal -n "__fish_seen_subcommand_from tool; and not __fish_seen_subcommand_from $tool_subcommands" -a "expand" -d "show macro expansion for given location" -x
complete -c crystal -n "__fish_seen_subcommand_from expand" -s D -l define -d "Define a compile-time flag"
complete -c crystal -n "__fish_seen_subcommand_from expand" -s c -l cursor -d "Cursor location with LOC as path/to/file.cr:line:column"
Expand Down
17 changes: 17 additions & 0 deletions etc/completion.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ local -a cursor_args; cursor_args=(
'(-c --cursor)'{-c,--cursor}'[cursor location with LOC as path/to/file.cr:line:column]:LOC'
)

local -a include_exclude_args; cursor_args=(
'(-i --include)'{-i,--include}'[Include path in output]' \
'(-i --exclude)'{-i,--exclude}'[Exclude path in output]'
)

local -a programfile; programfile='*:Crystal File:_files -g "*.cr(.)"'

# TODO make 'emit' allow completion with more than one
Expand Down Expand Up @@ -158,6 +163,7 @@ _crystal-tool() {

commands=(
"context:show context for given location"
"dependencies:show tree of required source files"
"expand:show macro expansion for given location"
"format:format project, directories and/or files"
"hierarchy:show type hierarchy"
Expand All @@ -183,6 +189,17 @@ _crystal-tool() {
$cursor_args
;;

(dependencies)
_arguments \
$programfile \
$help_args \
$no_color_args \
$exec_args \
'(-f --format)'{-f,--format}'[output format 'tree' (default), 'flat', 'dot', or 'mermaid']:' \
$prelude_args \
$include_exclude_args
;;

(expand)
_arguments \
$programfile \
Expand Down
23 changes: 22 additions & 1 deletion man/crystal.1
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,33 @@ Disable colored output.
.Op --
.Op arguments
.Pp
Run a tool. The available tools are: context, format, hierarchy, implementations, and types.
Run a tool. The available tools are: context, dependencies, format, hierarchy, implementations, and types.
.Pp
Tools:
.Bl -tag -offset indent
.It Cm context
Show context for given location.
.It Cm dependencies
Show tree of required source files.
.Pp
Options:
.Bl -tag -width "12345678" -compact
.Pp
.It Fl D Ar FLAG, Fl -define= Ar FLAG
Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given.
.It Fl f Ar FORMAT, Fl -format= Ar FORMAT
Output format 'tree' (default), 'flat', 'dot', or 'mermaid'.
.It Fl i Ar PATH, Fl -include= Ar PATH
Include path in output.
.It Fl e Ar PATH, Fl -exclude= Ar PATH
Exclude path in output.
.It Fl -error-trace
Show full error trace.
.It Fl -prelude
Specify prelude to use. The default one initializes the garbage collector. You can also use --prelude=empty to use no preludes. This can be useful for checking code generation for a specific source code file.
.It Fl -verbose
Show skipped and heads of filtered paths
.El
.It Cm expand
Show macro expansion for given location.
.It Cm format
Expand Down
42 changes: 37 additions & 5 deletions src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Crystal::Command
expand show macro expansion for given location
format format project, directories and/or files
hierarchy show type hierarchy
dependencies show file dependency tree
implementations show implementations for given call in location
unreachable show methods that are never called
types show type of main variables
Expand Down Expand Up @@ -182,6 +183,9 @@ class Crystal::Command
when "hierarchy".starts_with?(tool)
options.shift
hierarchy
when "dependencies".starts_with?(tool)
options.shift
dependencies
when "implementations".starts_with?(tool)
options.shift
implementations
Expand Down Expand Up @@ -341,9 +345,11 @@ class Crystal::Command
hierarchy_exp : String?,
cursor_location : String?,
output_format : String?,
dependency_output_format : DependencyPrinter::Format,
combine_rpath : Bool,
includes : Array(String),
excludes : Array(String) do
excludes : Array(String),
verbose : Bool do
def compile(output_filename = self.output_filename)
compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename))
compiler.compile sources, output_filename, combine_rpath: combine_rpath
Expand All @@ -356,7 +362,8 @@ class Crystal::Command

private def create_compiler(command, no_codegen = false, run = false,
hierarchy = false, cursor_command = false,
single_file = false, path_filter = false)
single_file = false, dependencies = false,
path_filter = false)
compiler = new_compiler
compiler.progress_tracker = @progress_tracker
link_flags = [] of String
Expand All @@ -369,8 +376,10 @@ class Crystal::Command
hierarchy_exp = nil
cursor_location = nil
output_format = nil
dependency_output_format = nil
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
excludes = [] of String
includes = [] of String
verbose = false

option_parser = parse_with_crystal_opts do |opts|
opts.banner = "Usage: crystal #{command} [options] [programfile] [--] [arguments]\n\nOptions:"
Expand Down Expand Up @@ -414,8 +423,27 @@ class Crystal::Command
end
end

opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f|
output_format = f
if dependencies
opts.on("-f tree|flat|dot|mermaid", "--format tree|flat|dot|mermaid", "Output format tree (default), flat, dot, or mermaid") do |f|
dependency_output_format = DependencyPrinter::Format.parse?(f)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is the below mentioned above)

error "Invalid format: #{f}. Options are: tree, flat, dot, or mermaid" unless dependency_output_format
end

opts.on("-i <path>", "--include <path>", "Include path") do |f|
includes << f
end

opts.on("-e <path>", "--exclude <path>", "Exclude path (default: lib)") do |f|
excludes << f
end

opts.on("--verbose", "Show skipped and filtered paths") do
verbose = true
end
else
opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f|
output_format = f
end
end

opts.on("--error-trace", "Show full error trace") do
Expand Down Expand Up @@ -561,6 +589,8 @@ class Crystal::Command
end
end

dependency_output_format ||= DependencyPrinter::Format::Tree

output_format ||= "text"
unless output_format.in?("text", "json")
error "You have input an invalid format, only text and JSON are supported"
Expand All @@ -577,7 +607,9 @@ class Crystal::Command
end

combine_rpath = run && !no_codegen
@config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format, combine_rpath, includes, excludes
@config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename,
arguments, specified_output, hierarchy_exp, cursor_location, output_format,
dependency_output_format.not_nil!, combine_rpath, includes, excludes, verbose
end

private def gather_sources(filenames)
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ module Crystal
# Whether to link statically
property? static = false

property dependency_printer : DependencyPrinter? = nil

# Program that was created for the last compilation.
property! program : Program

Expand Down
16 changes: 16 additions & 0 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,22 @@ module Crystal
recorded_requires << RecordedRequire.new(filename, relative_to)
end

def run_requires(node : Require, filenames) : Nil
dependency_printer = compiler.try(&.dependency_printer)

filenames.each do |filename|
unseen_file = requires.add?(filename)

dependency_printer.try(&.enter_file(filename, unseen_file))

if unseen_file
yield filename
end

dependency_printer.try(&.leave_file)
end
end

# Finds *filename* in the configured CRYSTAL_PATH for this program,
# relative to *relative_to*.
def find_in_path(filename, relative_to = nil) : Array(String)?
Expand Down
41 changes: 23 additions & 18 deletions src/compiler/crystal/semantic/semantic_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,11 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor

if filenames
nodes = Array(ASTNode).new(filenames.size)
filenames.each do |filename|
if @program.requires.add?(filename)
parser = @program.new_parser(File.read(filename))
parser.filename = filename
parser.wants_doc = @program.wants_doc?
begin
parsed_nodes = parser.parse
parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?)
# We must type the node immediately, in case a file requires another
# *before* one of the files in `filenames`
parsed_nodes.accept self
rescue ex : CodeError
node.raise "while requiring \"#{node.string}\"", ex
rescue ex
raise Error.new "while requiring \"#{node.string}\"", ex
end
nodes << FileNode.new(parsed_nodes, filename)
end

@program.run_requires(node, filenames) do |filename|
nodes << require_file(node, filename)
end

expanded = Expressions.from(nodes)
else
expanded = Nop.new
Expand All @@ -98,6 +84,25 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor
false
end

private def require_file(node : Require, filename : String)
parser = @program.new_parser(File.read(filename))
parser.filename = filename
parser.wants_doc = @program.wants_doc?
begin
parsed_nodes = parser.parse
parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?)
# We must type the node immediately, in case a file requires another
# *before* one of the files in `filenames`
parsed_nodes.accept self
rescue ex : CodeError
node.raise "while requiring \"#{node.string}\"", ex
rescue ex
raise Error.new "while requiring \"#{node.string}\"", ex
end

FileNode.new(parsed_nodes, filename)
end

def visit(node : ClassDef)
check_outside_exp node, "declare class"
pushing_type(node.resolved_type) do
Expand Down
Loading