Module: Jade::CLI::Q

Defined in:
lib/jade/cli/q.rb

Overview

Headless query interface to the same compiler intelligence the LSP server uses. Intended for tools and agents that want a quick JSON answer instead of speaking JSON-RPC.

Defined Under Namespace

Classes: Context

Constant Summary collapse

USAGE =
<<~TXT.freeze
  Usage: jade q COMMAND [ARGS]

    hover   FILE:LINE:COL    Type info at the given position.
    symbols FILE             Document symbols (outline) for FILE.
    defn    FILE:LINE:COL    Goto-definition location.
    refs    FILE:LINE:COL    Find all references (incl. declaration).
    api     [MODULE|NAME]    Stdlib signatures. No argument lists the
                             modules; `Maybe` describes one; `List.map`
                             picks a single symbol.
    find    TERM             Symbols whose name matches TERM.
    syntax  [FORM]           How a form is written (`lambda`, `case`,
                             `interface`, …). No argument lists them all.

  FILE is relative to the project root (cwd).
  LINE and COL are 0-indexed (LSP convention).

  `api` and `find` cover the stdlib plus, inside a project, its own and
  its extensions' modules. `syntax` needs nothing at all. Everything
  else compiles the project, caching at .jade/cache so repeat queries
  are fast.
TXT

Class Method Summary collapse

Class Method Details

.api(name) ⇒ Object



52
53
54
55
56
57
58
59
# File 'lib/jade/cli/q.rb', line 52

def api(name)
  Api.load.then do |api|
    next listing(api) unless name

    (api.describe(name) || api.lookup(name))
      .then { it || fail("no module or symbol named #{name}") }
  end
end

.defn(arg) ⇒ Object



104
105
106
107
108
109
110
111
# File 'lib/jade/cli/q.rb', line 104

def defn(arg)
  file, line, col = parse_position(arg)
  Context.new(file).then do |ctx|
    Jade::LSP::Converters.definition_for_path(
      ctx.path_at(line, col), ctx.registry, ctx.entry, ctx.source_root
    )
  end
end

.dump(result) ⇒ Object



130
131
132
# File 'lib/jade/cli/q.rb', line 130

def dump(result)
  puts JSON.pretty_generate(result)
end

.find(term) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/jade/cli/q.rb', line 61

def find(term)
  fail 'TERM required' unless term

  Api
    .load
    .then { { matches: it.search(term), **skipped(it) } }
end

.hover(arg) ⇒ Object



85
86
87
88
89
90
91
92
# File 'lib/jade/cli/q.rb', line 85

def hover(arg)
  file, line, col = parse_position(arg)
  Context.new(file).then do |ctx|
    Jade::LSP::Converters.hover_for_path(
      ctx.path_at(line, col), ctx.registry, ctx.entry
    )
  end
end

.listing(api) ⇒ Object



77
78
79
# File 'lib/jade/cli/q.rb', line 77

def listing(api)
  { modules: api.modules, **skipped(api) }
end

.parse_position(arg) ⇒ Object



123
124
125
126
127
128
# File 'lib/jade/cli/q.rb', line 123

def parse_position(arg)
  fail 'FILE:LINE:COL required' unless arg
  fail "expected FILE:LINE:COL, got #{arg.inspect}" unless arg.count(':') == 2

  arg.split(':').then { |(file, line, col)| [file, line.to_i, col.to_i] }
end

.refs(arg) ⇒ Object



113
114
115
116
117
118
119
120
121
# File 'lib/jade/cli/q.rb', line 113

def refs(arg)
  file, line, col = parse_position(arg)
  Context.new(file).then do |ctx|
    Jade::LSP::Converters.references_for_path(
      ctx.path_at(line, col), ctx.registry, ctx.entry, ctx.source_root,
      include_declaration: true,
    )
  end
end

.run(argv) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/jade/cli/q.rb', line 37

def run(argv)
  case argv[0]
  when 'hover'   then dump(hover(argv[1]))
  when 'symbols' then dump(symbols(argv[1]))
  when 'defn'    then dump(defn(argv[1]))
  when 'refs'    then dump(refs(argv[1]))
  when 'api'     then dump(api(argv[1]))
  when 'find'    then dump(find(argv[1]))
  when 'syntax'  then dump(syntax(argv[1]))
  else
    warn USAGE
    exit 1
  end
end

.skipped(api) ⇒ Object



81
82
83
# File 'lib/jade/cli/q.rb', line 81

def skipped(api)
  api.skipped.empty? ? {} : { skipped: api.skipped }
end

.symbols(file) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'lib/jade/cli/q.rb', line 94

def symbols(file)
  fail 'FILE required' unless file

  Context.new(file).entry.then do |entry|
    entry.ast.body.expressions.filter_map do |node|
      Jade::LSP::Converters.to_document_symbol(node, entry.source)
    end
  end
end

.syntax(form) ⇒ Object



69
70
71
72
73
74
75
# File 'lib/jade/cli/q.rb', line 69

def syntax(form)
  return { forms: LSP::Snippets.catalog } unless form

  LSP::Snippets
    .find(form)
    .then { it || fail("no syntax form named #{form}") }
end