Skip to content

Commit

Permalink
Feature: Add crystal tool dependencies (crystal-lang#13631)
Browse files Browse the repository at this point in the history
Co-authored-by: Quinton Miller <nicetas.c@gmail.com>
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
Co-authored-by: Caspian Baska <email@caspian.computer>
  • Loading branch information
4 people authored and Blacksmoke16 committed Dec 11, 2023
1 parent b113d66 commit 35b1ffd
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 25 deletions.
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
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)
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

0 comments on commit 35b1ffd

Please sign in to comment.