Repository navigation
Releases
v0.10.0
- Upgrading reindexes once (store v75): the first command after
upgrading rebuilds the index, with nothing to run. A script reading
--dead'ssummary.tiersnow sees methods undertest-only(below).
Added
-
Haml templates are read. Every
*.hamlin a checkout is indexed for
the Ruby it runs, at the template's own lines and columns, as an ERB
template is: a script line's code (- x,= x,!= x,&= x,~ x, a
tag's= x, and the lines a trailing comma continues it onto), a tag's
attribute hash and(name=value)values, each#{…}in text and in a
filter, and a:rubyfilter's body. A call in one underapp/views/runs
on the view context (resolved_via: view), so--defand--refs
answer there, and--deadcounts it as a call rather than "named in a
view (…), which is not read" — that caveat stays for Slim, Jbuilder and
Builder. Anif/elsif/else/unlesschain is read flat, its
conditions and branches each a statement. Adoblock's parameters are
in scope inside it and not after it, except where two blocks end at the
left margin at once, which leaves room to close only one: there the
outer block's parameters stay in scope over the Ruby up to its close.
The language server reads.hamldocuments and says so
(experimental.trekr.templateslists"haml"), and the Claude Code
plugin starts it for.hamlfiles. The VS Code extension's Haml support
ships separately (see its changelog). -
--deadlists a method only specs and tests call astest-only, as
it already did a class, however many calls they write. Such a method was
missing when the specs called it more than once, andsingle-callerwhen
once. The reason says how many calls and where the first is, and a method
row'stest_refscounts them, as a constant row's does. A test is a file
under aspec/,test/ortests/directory at the checkout's root or
a nested package's (plugins/chat/spec/), not one insidelib/or
app/. A method that overrides another, or a hook Ruby calls by name,
keeps the tier it would have without the tests, as does one a route or a
library's convention reaches (a Pundit predicate), and a spec's own
helper is nevertest-only. -
--deadsays when a row is reached from outside the repo. Such a row
keeps its tier, is gradedlower, and names its kind of entry point
(entry_pointin JSON); its caveat names the evidence that settles it.route: a routed controller action, or a controller only routes name
(these wereconvention-only, clear). Settle it with request logs or
APM.job: an Active Job or Sidekiq job class nothing outside the tests
names. Settle it with job metrics or logs.api: a library's public API — a public or protected method (RDoc
documents protected ones), a class or module, or a constant bound to a
constant path, resolved or not (Worker = Job,Client = OtherGem::Client), or to aDeprecatedConstantProxy, in a library's
lib/. Any other value constant is not API. Settle it with the gem's
dependents or usage data.
A library's
lib/is the checkout's own, or one beside a nested
*.gemspec(protobuf'sruby/), in a checkout that is no app. An app has
aconfig/application.rbat its root, gemspec or not, or aconfig.ru
or anapp/without a root*.gemspec— so an engine gem'sapp/or a
gem's democonfig.rudoes not make one. A library needs no gemspec: a
Hoe gem such as minitest has none until release. An app's own engine is
not a library.What its author marks internal is not API:
:nodoc:,@api private,
@private(a YARD tag opening a comment line),Internal:(a TomDoc
status opening the comment's first line), a leading underscore, or
private_constant. Each mark reaches as far as its tool reads it:- A
:nodoc:covers its own class and the methods its body defines, as
RDoc reads it, so Rails'module ActiveSupport # :nodoc:hides nothing
nested in it. In that body, an own-line# :doc:at a method's
indentation uncovers the method, unless an own-line# :nodoc:at that
indentation comes between (Rails'Dependencies.run_interlock). :nodoc: alland@api privatealso cover what their marked opening
holds, but not another opening of the class;private_constantcovers
what is nested anywhere.- A method's own
@api publicoverrides its class's@api private, as
YARD reads it. - A private or underscored method marked
:doc:is API, as RDoc
documents it (Rails'RequestForgeryProtection#verified_request?). - A method under a bare
module_functionis API by its public singleton
copy, as one named bymodule_function :xis, until a bareprivate
ends that mode.
-
An RSpec predicate matcher is a reference to the predicate it calls.
expect(page).to have_event_count(1)callshas_event_count?, and
be_visibleandbe_an_admincallvisible?andadmin?.be_exist
callsexist?when the subject has it, elseexists?, as RSpec's
method_missingdoes.--refslists each such call (called_asnames
the matcher), the editor's Find References lists it too, and--dead
counts it, so a page object's predicate a spec asserts this way is no
longerunreferenced. Confirmed whereexpect(x)types its subject,
possible where it does not (be_existthen for bothexist?and
exists?); a matcher RSpec or the spec defines (be_within) calls no
predicate. -
--deadlists a Minitest or Test::Unit test asconvention-only,
by the test runner, rather thanunreferenced: a publictest_method
of a class under a test directory that inheritsMinitest::Testor its
kin, or, where its ancestors are not all indexed, is in a test file
(test_*.rb,*_test.rb) undertest/. A class in a test file that is
no test case (a fake, a stub controller) runs notest_method. -
--deadreads an extensionless Ruby script (exe/tool,bin/x),
which trekr does not index, for the names it writes: a row one names
keeps its tier and islower, "named in a script (exe/tool), which is
not read", as a Slim view's word grades it. -
--deadreads an interpolated string sent by name,
TopicQuery.new(opts).public_send("list_#{filter}")orrule = "validate_#{name}"; public_send(rule), as it reads:"list_#{filter}".
A method whose name fits, in the receiver's class, its ancestors, its
subclasses or theirs, says "a name of its shape is sent at runtime" and
is gradedlower. A string sent to a variable
(row.public_send("total_#{kind}")) is left alone.
Changed
--deadgrades a class, module or constant rowclearwhen nothing in
the checkout could reach it by a built name, instead ofloweron
every row. A row islowerwhen the checkout looks up a name computed
whole (name.constantize,Object.const_get(x),klass.const_get(x)),
and its caveat says where, or when a namespace, shape or listing already
caveated reaches it. Apps nearly always have such a lookup, so their rows
staylower; a gem without one now readsclear.--deadgrades a module's methodlowerwhen its name is called on a
class that lacks it, naming the call: "called on TopicQuery (…), where
trekr finds no such method; a module mixed in at runtime may supply it".
A module prepended from a block (a plugin'safter_initialize) is no
edge trekr records, so such a method wasunreferenced, clear, though
that call runs it.
Fixed
- A bare
private,protectedorpublicendsmodule_function's
mode, as in Ruby: a method after one is no longer recorded as a public
singleton copy plus a private instance method, so--symbolsand
--defgive it the visibility its toggle says, and no singleton method
Ruby never defines.private def x,public def xandprotected def xare no toggle: they keep the mode's public singleton copy, and the
instance method takes the visibility written. - A
definTarget.class_eval do … endinside another class or method
isTarget's, as it already was at the top level. It was recorded on
the class around the block, so calls to it were residue and--refs 'Target#x'saidno_such_method.Target.instance_eval do def xmakes
Target.x. Thedef's owner is found as Ruby findsTarget, through
the enclosing class's ancestors too, and an alias (Gizmo = Gadget) is
followed. A constant written in a qualified receiver's block
(Faker::Lorem.instance_eval { X = 1 }) is scoped like Ruby's: top-level
there, not the receiver's. --deadno longer lists a class or module that a spec or test file
defines and the tests use astest-only: it is test code in use, as a
spec's own helper method is. One nothing uses is stillunreferenced.--deadno longer takes atest/orspec/directory insidelib/or
app/for tests: rack-test's library islib/rack/test/, so a constant
only it used wastest-only, and a name it builds and sends was not read.--deadno longer says a method "callssuper, so it overrides a method
trekr has not indexed", and grades itlower, when thesuperbelongs
to adefnested in its body or to a block it hands todefine_method
ordefine_singleton_method(send(:define_method, :x) { super() }).--deadcounts a test's reference to a class once. A class written with
a compact namespace nothing else defines (class Scheduler::X) had each
reference counted twice intest_refsand its "referenced only from
tests (N, …)" reason.
v0.9.8
v0.9.7
v0.9.6
- Replaced versions of files are no longer kept forever. A version an
edit, a branch switch or--dropleft behind was kept so a switch back
would not reparse — and never collected, so a store in a repo edited
between indexes grew by every edit (rails, 100 files edited a day: +1.1
MB a day, without end). It is now kept for 14 days after nothing maps
it, then collected by the background cleanup or--gc; on rails the
same edits level off at day 14. A switch back within the retention still
reuses everything; after it, the files are read again (2,611 rails files
in about 2 s). An existing index loses nothing on upgrade: what it
already holds is counted from then. TREKR_RETENTIONsets how long —36h,14d,2w,0, ornever
to keep every version as before — and--retentionoverrides it for one
--gcor--status. Set it where every trekr sees it: the background
cleanup reads the environment, not the flag.--gc --older-thannow judges checkouts only. A query's copy of an
edit went at--older-than; it now goes by the retention, as every
unmapped version does. To collect them all now,trekr --gc --retention 0.--statusreports what is kept (storage.retained:blobs,expired,
retention);--gcreportsretentionand, among itsblobs, the
expiredones.- An index no longer fails when a collection runs beside it. An index
that found a file's version known, then waited for the write lock while
--gcor the background cleanup deleted that version, failed "Query
returned no rows" (exit 74). It now decides what is known once it holds
the lock, and reads such a file again. An editor's save of a version a
collection had just deleted kept the file's previous version until the
next save; it is now parsed. --older-thanrefuses an age too long to count (exit 64), as
TREKR_RETENTIONand--retentiondo; it wrapped round to a short one.
v0.9.5
- The index cleans up after itself. An upgrade's rebuild left its old
rows' space free inside the file (a 229 MB store stayed 229 MB holding
75 MB), and each release left a stale tree snapshot for every checkout
not queried since; onlytrekr --gc --vacuum, which nothing mentioned,
gave them back. Now, after an index pass — the one a query or the editor
starts, ortrekr --index— trekr measures the waste at most every two
hours, and when it is at least 32 MB and a quarter of the file, a
background process removes the stale snapshots, collects the blobs only
a query read a week ago, and gives the free space back to the disk a few
megabytes at a time, so an index or edit waits a fraction of a second,
not for the whole compaction. A store from before this release is
rewritten once instead (through a temporary file, when the disk has room
for twice its live size), which switches it. It never runs on a query,
waits for nothing, and skips a store another trekr is writing. Nothing
to do: a store 0.9.4 rebuilt shrinks after its next index. --gccollecting many checkouts is ~35× faster. Each blob it
deleted scanned a table of class-body calls: removing 240 checkouts from
a 1 GB index took 34 minutes, and takes 56 s now. An existing index gains
the missing index in the background, or at the start of the next--gc
— a dry run included, which says so (indexes) and removes nothing;
nothing is rebuilt.--statussays how big the index is and what it could give back
(storagein--json), when the background cleanup last ran, and why
it left space it meant to give back (no_room: the disk lacks room to
rewrite the index once).--gc'sreclaimed_bytesis what left the disk — the snapshots,
copies and stores it removed, and the file's shrink after--vacuum. It
said 0 after removing snapshots, since it counted only the pages freed
inside the file; those arefreed_bytesnow, andfree_bytesis what is
left free.- A question that indexes its checkout first no longer sometimes builds
the tree twice. When the index it started finished its rows before the
query's first look, the query stopped waiting for it and assembled the
tree itself while the index prepared the same one. - Hover at a constant's definition in
class Objectnames itOBJC,
as hover at a mention already did; it saidObject::OBJC.
v0.9.4
- A constant written in
class Objectis the top level's. Ruby reaches
class Object; OBJC = 6; endasOBJC,::OBJCandObject::OBJC
alike, and a top-levelTOP = 1asObject::TOP; trekr kept them apart,
so::OBJC,Object::TOP, and a bareOBJCat the top level or in a
module's method were residue, and--refsby each spelling listed a
different set. They now resolve, hover and go to the definition, and
--refs ::OBJC,--refs Object::OBJCand Find References at any of them
list the same mentions. Hover at a mention names such a constantOBJC,
as Ruby does.
A module's lookup, and::KC, also reach a Kernel constant now, as in
Ruby;Box::OBJCandBox::TOPstill miss. --deadcounts a top-level constant used asObject::Xor::X.
class Object; LIMIT = 1; endread only as::LIMIT, orTOP = 1read
only asObject::TOP, was listed unreferenced.--refsby a constant's whole name lists mentions made through an
ancestor.--refs Kernel::KCmissedBox::KCandLeaf::KC, which
Ruby resolves through Box's and Leaf's ancestors, and so did--refsat
its definition, while Find References in an open file listed them. Every
mention that resolves to the constant is now listed, by name, at the
definition and at any mention, in the CLI and the editor alike. The store
is rebuilt on first use (v66): the next command reindexes.
The rebuild leaves the old rows' space free inside the file; run
trekr --gc --vacuumonce afterwards to shrink it.
v0.9.3
Box::OBJCno longer finds a constant written inclass Object.
Ruby (since 2.5) raises NameError when a scoped lookup reaches a constant
only through Object, but--def, hover, Go to Definition and Find
References resolved it toObject::OBJC, and the--refs Box::OBJC
miss said it was found through Box's ancestor Object.Object::OBJC, a
bareOBJCinside a class, and a Kernel constant reached asBox::KC
still resolve, as in Ruby.
v0.9.2
- A new file opened while git is slow for another lands with its open.
In the editor, a file git placed at once was written only at the next
retry, a quarter second later, whenever another file'sgit ls-fileswas
still out, so the request right after its open missed it. Opens behind a
pending answer now share a 100 ms wait. - A path gem's copy of a stdlib file answers alone.
require "json"
where a Bundlerpath:gem has its ownlib/json.rbanswered that copy
and the stdlib's, ambiguous; Bundler puts a path gem'slib/ahead of
the stdlib as surely as an installed gem's, so it is now resolved. A copy
in the checkout's ownlib/,test/orspec/is still listed beside
the stdlib's, since those are only guessed to be on the load path. - A
--refsmiss on a name reached through an ancestor says where it
is.--refs File::NULLread as if nothing defined it; the mentions are
listed underIO::NULL, where Ruby finds it throughFile < IO. The
miss now adds "found through File's ancestor IO, it is IO::NULL" (or,
through an alias, the name Ruby's lookup reaches). Which rows are found
is unchanged.
v0.9.1
-
A constant assigned in
class << selfis the singleton class's.
--refs,--def,--dead, hover and Go to Definition took it for the
class's own, soWidget::LIMITand an instance method'sLIMITwere
listed as its references, though Ruby raises NameError on both. It is now
found only from inside that body, by position or by Ruby's own spelling
of its whole name,--refs Widget::singleton_class::LIMIT
(--refs Widget::LIMITmisses, and says the name it has);--deadand
workspace symbol search name its ownerWidget::singleton_class. A class
opened in that body is named the same way, and so are its methods:
--refs 'Widget::singleton_class::Job#perform'. Aclass << selfmethod
no longer resolves a constant of the class's superclass or includes,
which Ruby does not find there either. -
This changes what an index records (store v65): trekr reindexes once after
upgrading. -
A file opened behind a slow git is indexed once git answers. In
the editor, a new file whosegit ls-filestook over 1 s was left out
of the index for the session, and each open or save held every request
for that second. git is now asked off the request loop and waited for at
most 400 ms, once for files opened together, and the file lands when it
answers; a git that has not answered in 10 s is stopped and
the next save asks again. An ignored file is still never written.
v0.9.0
-
index.causesays when another trekr was writing. An answer whose
edited files could not be read because another trekr held the store
saidstale: truewith nocause;causenow names those files and
why. -
A call inside a made name's
#{…}is that call. In
class_eval("def #{dir.upcase}_shout"), every letter ofupcase
answered the method the string makes (UP_shout), with full
confidence. A call or constant written inside such a name now answers
itself, as a variable there already did. -
Go to Definition in a file the index has not read uses its own
class. In an untracked file created after the last index (with no
file watcher to say so), a call inherited from the file's superclass
listed every method of that name;--defanswered the superclass's.
Opening a file now reads it as saving it does — one the index would
read, that is: opening or saving a file git ignores leaves the index
as it was. -
--gccollects what queries recorded. Each edited version of a file
a query read stayed in the store for good;--gcnow removes those no
checkout maps once they are older than--older-than(and than an hour,
so a query still reading one keeps it), and exits 0 when that is all it
removed. Blobs an--indexread or mapped are kept as before. -
A spec macro's or
store_accessor's method ends where its argument
does. The column just pastlet(:name),subject(:name)or
RSpec::Matchers.define :nameanswered that method, and
store_accessor :settings, :"key"placed each accessor with its own
name's length from the:— short of the closing quote forkey, far
past it forkey_changed?. Each now spans its symbol (or string) as
written, closing quote included. An association's generated methods
(dogs=,dog_idsfromhas_many :dogs) still reach past it. -
--refsby a constant's whole name lists the constant as its scopes
write it.--refs Admin::Widgetlisted only mentions written
Admin::Widget, missingWidgetinsidemodule Admin; on
SEPARATORS.joininsidemodule Routerit said "no mention of
Router::SEPARATORS"; and on a constant's definition (SIZES = …) it was a
usage error, exit 64. A name written with::now lists the definitions
and the references Ruby's lookup resolves to it through the scopes they
are written in, as the editor's Find References does, and a position on
any class, module or constant answers that way. A constant nothing
defines whole is named by the part that resolves:Nodes::Unioninside
module ArelisArel::Nodes::Union. By name, a path through an
includer, a subclass or an alias (Child::K) is not found; at the
mention it is. A top-level constant's whole name is::Widget; a bare
--refs Widgetstill lists every mention of the name. -
A
--refsmiss is said as a no. "no mention of X (indexed? try
trekr --index)" sent an agent to reindex a checkout already indexed;
it now reads "no mention of X in what trekr indexed", and for a name
written with::says it was read as a whole name from the top level. -
--defin arequirestring answers the file it loads. Anywhere in
require_relative "../../lib/helper",--defsnapped to
Kernel#require_relative, while Go to Definition openedlib/helper.rb.
Both now ask one subject: the file, at its top, withunder: requireand
resolved_via: requirein--json, and areasonwhen no file is found.
A gem's copy of a stdlib file (require "json","erb","uri") is
the one answered, as Ruby loads it; Go to Definition offered both. In a
monorepo, a file inside a path gem also searches that gem's owntest/
andspec/:require "cases/helper"in rails'activerecord/test
opensactiverecord/test/cases/helper.rb. -
--defon a method'sdefnames its Sorbet signature. Go to
Declaration from adefopened the.rbistub that describes it, while
--defthere answered only thedefitself. Its--jsonnow carries
signatures, as an answer on a call of the method does.