Why?
====
With multiple calls to `parallel`, `stopGlobalPool` stops working
correctly.
This moves `stopGlobalPool` higher up, and executes it once, allowing
multiple calls to `parallel` to happen without causing issues.
This introduces a monad transformer stack to cover our reader (options
from the CLI) and except (for handling failure cases, initially missing
tags or invalid config).
This ensures errors are bubbled up appropriately (and halt program
execution) and the Options are available in the correct locations within
the app.
This also separates options parsing (which remains in app/Main.) from
translating those options into the correctly executed runner and
generated output.
This enables per-user and per-project configs, located in:
* ~/.unused.yml
* APP_ROOT/.unused.yml
Configurations stack upon each other, not replace; unused provides a
very base config, but additional configurations can be defined.
Per-user configs are best used to suit common types of projects at a
generic level. For example, a developer commonly working in Rails
applications might have a config at ~/.unused.yml for patterns like
Policy objects from Pundit, ActiveModel::Serializers, etc.
Per-project config would be less-generic patterns, ones where re-use
isn't likely or applicable.
See unused's global config:
https://github.com/joshuaclayton/unused/blob/master/data/config.yml
The structure is as follows:
- name: Rails
autoLowLikelihood:
- name: Pundit
pathStartsWith: app/policies
pathEndsWith: .rb
termEndsWith: Policy
classOrModule: true
- name: Pundit Helpers
pathStartsWith: app/policies
allowedTerms:
- Scope
- index?
- new?
- create?
- show?
- edit?
- destroy?
- resolve
- name: Other Language
autoLowLikelihood:
- name: Thing
pathEndsWith: .ex
classOrModule: true
Name each item, and include an autoLowLikelihood key with multiple named
matchers. Each matcher can look for various formatting aspects,
including termStartsWith, termEndsWith, pathStartsWith, pathEndsWith,
classOrModule, and allowedTerms.
Why?
====
In cases where no matchers are present, a language config should not
auto-classify every match as low-likelihood; instead, it should return
False so subsequent checks can operate on the match itself.
This is related to 9bf9499e67f52bcde2420bfe3945f73cfdaa06d7; both are
handling cases where less configuration data than ideal is present and
the program still needs to operate correctly.
Why?
====
If parsing options fails, the program will exit; if
withoutCursor has been called prior to execParser, the program may exit
without any way to re-enable the cursor. This can cause confusion and
frustration for users.
Why?
With SHA fingerprinting speeds improved drastically by
f618d8a796, we can now re-enable
caching by default.
This introduces a -C flag to disable the cache for a run.
Note that the cache is always invalidated when files are modified.
Why?
====
Dynamic languages, and Rails in particular, support some fun method
creation. One common pattern is, within RSpec, to create matchers
dynamically based on predicate methods. Two common examples are:
* `#admin?` gets converted to the matcher `#be_admin`
* `#has_active_todos?` gets converted to the matcher `#have_active_todos`
This especially comes into play when writing page objects with predicate
methods.
This change introduces the concept of aliases, a way to describe the
before/after for these transformations. This introduces a direct swap
with a wildcard value (%s), although this may change in the future to
support other transformations for pluralization, camel-casing, etc.
Externally, aliases are not grouped together by term; however, the
underlying counts are summed together, increasing the total occurrences
and likely pushing the individual method out of "high" likelihood into
"medium" or "low" likelihood.
Closes#19.
Why?
====
This ensures no method/function bleed between languages, which may cause
confusing miscalculation when methods/functions are reused across
different types of projects (e.g. index from Rails migrations and index
action from Phoenix controllers).
Why?
====
When printing results, the column formatter has to be configured at the
topmost level (where it has all result data) to calculate widths
appropriately; however, it's only used layers deep, when rendering the
columns themselves.
This moves the formatter into a ReaderT so the configuration can be
passed around appropriately.
Why?
====
Parsec is overkill when all that's really needed is splitting on
semicolons and converting a string to a non-negative Int.
One side-effect of this is to convert the caching mechanism from flat
text to CSV, with cassava handling (de-)serialization.
Additional
==========
Introduce ReaderT to calculate sha once per cache interaction
Previously, we were calculating the fingerprint (SHA) for match results
potentially twice, once when reading from the cache, and a second time
if no cache was found. This introduces a ReaderT to manage cache
interaction with a single fingerprint calculation.
This also abstracts what's being cached to only care about the fact that
the data can be converted to/from csv.
Why?
====
Because a .gitignore file captures a fair number of project-specific
directories and files to ignore, we can use this list to reduce the
number of files to look at when determining a fingerprint for a project.
Because the fingerprint should be based on files we care about changing,
the project-specific .gitignore is a great place to start.
This drastically reduces fingerprint timing - for larger projects, or
projects with a massive number of files (e.g. anything doing anything
significant with NPM and a front-end framework), this will help make
caching usable. For normal projects, this cuts fingerprint
calculation to 10%-20% of what it was previously.
Closes#38
Why?
====
Frequency of a tool's usage is determined by how easy it is to use the
tool. By having to pipe in ctags files all the time, and not provide any
guidance to the user, this program is merely a toy, since it's hard to
get right, and harder to explore.
This modifies the default behavior to look for a ctags file in a few
common locations, and lets the user choose a custom location if she so
chooses.
Resolves#35
Why?
====
Handling low likelihood configuration was previously a huge pain,
because the syntax in Haskell was fairly terse. This introduces a yaml
format internally that ships with the app covering basic cases for
Rails, Phoenix, and Haskell. I could imagine getting baselines in here
for other languages and frameworks (especially ones I've used and am
comfortable with) as a baseline.
This also paves the way for searching for user-provided additions and
loading those configurations in addition to what we have here.
Why?
====
This library converts lots of strings to positive integers
(specifically, when it's parsing output from search results). Because
these are always positive integers, we can make more assumptions about
the data and how to parse the values.
Corresponding benchmark: https://gist.github.com/joshuaclayton/767c507edf09215d08cdd79c93a5f383
Why?
====
Calculating the SHA of the entire tree can be expensive; this shifts
reading from/writing to the cache to be configured via a switch in the
CLI.
In the future, it might make sense to store metadata about the repo,
including historical time to calculate both the SHA and non-cached
versions, to compare and choose which one to do intelligently.
At some point, this also needs to md5 the tags list itself and factor
that in (since if the tagging algorithm changes, and new tokens get
uncovered, it'd invalidate the cache)