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

Introduce :overrides option #152

Closed
wants to merge 7 commits into from
Closed

Introduce :overrides option #152

wants to merge 7 commits into from

Conversation

vemv
Copy link
Contributor

@vemv vemv commented Apr 1, 2020

Brief

Implements #134 (comment)

Starting from this PR, :overrides becomes the primary API for customizing things: as a consumer, one should:

  • Use one of: git-status-formatter, branch-formatter, project-formatter
  • Pass :overrides to those if wanting to tweak something.

...and should not:

  • Use core/format! directly
  • Use the "factory" stuff introduced in this PR

TODO

  • Address XXXs
  • At this point, git-status-formatter, branch-formatter, project-formatter all deserve some test coverage.
  • Explore how much can we DRY git-status-formatter, branch-formatter, project-formatter
    • A nice 'overrides' implementation would allow absolute DRY.
    • Might be less prioritary for now
  • Create impl sub-nses for git-status-formatter, branch-formatter, project-formatter
    • The intended API is format-and-lint! / lint!
    • Things like speced/defn default-formatters are certainly not public
  • Deprecate old APIs (kwargs) via check!
  • Review doc (README)
  • Review examples (test-resources)

QA plan

  • git-status-formatter, branch-formatter, project-formatter all keep working
  • The examples work as advertised, customizing things as its code shows

Author checklist

  • I have QAed the functionality
  • The PR has a reasonably reviewable size and a meaningful commit history
  • I have run the branch formatter and observed no new/significative warnings
  • The build passes
  • I have self-reviewed the PR prior to assignment
  • Additionally, I have code-reviewed iteratively the PR considering the following aspects in isolation:
    • Correctness
    • Robustness (red paths, failure handling etc)
    • Modular design
    • Test coverage
    • Spec coverage
    • Documentation
    • Security
    • Performance
    • Breaking API changes
    • Cross-compatibility (Clojure/ClojureScript)

Reviewer checklist

  • I have checked out this branch and reviewed it locally, running it
  • I have QAed the functionality
  • I have reviewed the PR
  • Additionally, I have code-reviewed iteratively the PR considering the following aspects in isolation:
    • Correctness
    • Robustness (red paths, failure handling etc)
    • Modular design
    • Test coverage
    • Spec coverage
    • Documentation
    • Security
    • Performance
    • Breaking API changes
    • Cross-compatibility (Clojure/ClojureScript)

(spec/def ::members (spec/and (spec/coll-of ::member)
(fn [xs]
(let [ids (->> xs
(map (fn ^{::speced/spec (rcomp count #{0 1})} [x]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
(map (fn ^{::speced/spec (rcomp count #{0 1})} [x]
(map (speced/fn ^{::speced/spec (rcomp count #{0 1})} [x]

@@ -22,6 +25,23 @@ so that final users can locate them and configure them."
(check! (spec/keys :req-un [:formatting-stack.protocols.spec.member/id])
x))))
Copy link
Member

Choose a reason for hiding this comment

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

(speced/def-with-doc ::member
  "A 'member' of the stack that does something useful: a formatter, linter or processor.

'Strategies' and 'Reporters' are not members - instead they help members accomplish their purpose."
  (spec/or :id (spec/keys :req-un [:formatting-stack.protocols.spec.member/id])
           ;; we are facing a `reify`, which means that formatting-stack is being customized
           ;; In those cases, an :id is practically useless (since the point of :id is overriding f-s), so no validation needed:
           :reify ::reify))

This will work without calling check! / valid? in the spec.

Comment on lines +41 to +42
(assert (apply distinct? ids)
"Members should have unique ids"))
Copy link
Member

Choose a reason for hiding this comment

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

This spec should be refactored so it leverages clojure.spec more. which improves testing/debugging/extensibility

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will check what can be done. At the very least a pr-str ids is due

(try
(sut/apply-overrides members overrides)
(catch AssertionError _
::EXCEPTION))))
Copy link
Member

Choose a reason for hiding this comment

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

Why not rethrow this exception? Currently it'll swallow the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's an are test case that will expect this ::EXCEPTION, so these aren't omitted

(that's part of why I put these in caps - to make it more immediately visible that something is both declared and consumed)

Copy link
Member

Choose a reason for hiding this comment

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

Could we split that are up, so the ones expecting an exception can use thrown-with-msg? for a more specific test? (e.g. now any assertionerror will pass)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe I wouldn't split the are- a single are means more contrast, less apples-vs-oranges etc

Will look into making this are itself more accurate, should be possible

:in-background? in-background?)))

(defn lint-branch! [& {:keys [target-branch in-background? reporter]
(defn lint-branch! [& {:keys [target-branch in-background? reporter overrides]
Copy link
Member

Choose a reason for hiding this comment

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

Why nest formatters/linters/processors/strategies under override? They seem direct arguments for lint-branch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The first thing that comes to mind is that the config should be a single map, so that it can become data.

  • data (a single hashmap) is simpler than code (a more intrincate invocation consisting of multiple arguments)
  • a single hashmap can be (more) easily a resource, a piece of Lein config, etc.
    • this is important, as one will want to share config across projects.
      • I envision this being accomplished by creating a single dep, containing a .edn as a resource. Zero config or requires to be done for consumers: we just discover the presence of such a config file.
  • Hashmaps can be (deep) merged
    • Not hard to imagine that people can want a merge of f-s defaults <-> workplace defaults <-> personal defaults.

It's also good to emphasize the notion of overrides as:

  • a thing you should be aware of
    • if I can pass :linters separately, I can be unaware of the existence a deep-merged overrides system
    • unawareness -> confusion, surprises, etc
  • the sole intended API
    • More APIs -> more complexity (surface area for bugs)

Comment on lines +62 to +67
{formatter-overrides :formatters
linter-overrides :linters
processor-overrides :processors
{formatters-strategies :formatters
linters-strategies :linters
processors-strategies :processors} :strategies} :overrides}]
Copy link
Member

Choose a reason for hiding this comment

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

Can we deal with the overrides one level higher e.g. create a fn which applies overrides, then call format!?

That way format! does less, and doesn't have to change shape, except for the removal of defaults (which is a good thing imo)

processors
reporter
in-background?]}]
(speced/defn format! [& {:keys [^vector? strategies
Copy link
Member

Choose a reason for hiding this comment

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

Not a big fan of the factory-concept here. We could assoc the strategies in format! (they are impl detail anyway), which would remove the need of creating the formatters/etc here, retaining the vectors as params.

third-party-indent-specs is still an issue, suggestion posted below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could assoc the strategies in format! (they are impl detail anyway)

See my comment beginning with The first thing that comes to mind [...]


(speced/defn default-formatters [_
^vector? formatters-strategies
^map? third-party-indent-specs]
Copy link
Member

@thumbnail thumbnail Apr 12, 2020

Choose a reason for hiding this comment

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

Let's add a default for third-party-indent-specs in cljfmt/new and cider/new. Then we can remove the references to third-party-indent-specs.

Consumers should overwrite the entire member if they want to change the specs. (which is how other members should work anyway). wdyt?

(assoc :strategies (conj formatters-strategies
strategies/files-with-a-namespace
strategies/exclude-edn)))
(when (strategies/refactor-nrepl-available?)
Copy link
Member

@thumbnail thumbnail Apr 12, 2020

Choose a reason for hiding this comment

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

Can we solve this with override-configuration? e.g. adding the formatter when nrepl is available.

(defn format-and-lint-branch!
"Note that files that are not completely staged will not be affected.

You can override that with `:blacklist []`."
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The docstring is correctly reflects the intended behavior, however not linting unstaged files is suboptimal.

After all, linting things is safe, so there's no reason to omit it (and it tends to be surprising; has bit me in the past).

Should be easy to fix.

:in-background? in-background?)))

(defn lint-branch! [& {:keys [target-branch in-background? reporter]
(defn lint-branch! [& {:keys [target-branch in-background? reporter overrides]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The first thing that comes to mind is that the config should be a single map, so that it can become data.

  • data (a single hashmap) is simpler than code (a more intrincate invocation consisting of multiple arguments)
  • a single hashmap can be (more) easily a resource, a piece of Lein config, etc.
    • this is important, as one will want to share config across projects.
      • I envision this being accomplished by creating a single dep, containing a .edn as a resource. Zero config or requires to be done for consumers: we just discover the presence of such a config file.
  • Hashmaps can be (deep) merged
    • Not hard to imagine that people can want a merge of f-s defaults <-> workplace defaults <-> personal defaults.

It's also good to emphasize the notion of overrides as:

  • a thing you should be aware of
    • if I can pass :linters separately, I can be unaware of the existence a deep-merged overrides system
    • unawareness -> confusion, surprises, etc
  • the sole intended API
    • More APIs -> more complexity (surface area for bugs)

[& {:keys [target-branch in-background? reporter formatters blacklist overrides]
:or {target-branch "master"
in-background? (not (System/getenv "CI"))
reporter default-reporter}}]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could :pre that no :linters, :formatters etc are passed as an argument (b/c deprecation)

processors
reporter
in-background?]}]
(speced/defn format! [& {:keys [^vector? strategies
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could assoc the strategies in format! (they are impl detail anyway)

See my comment beginning with The first thing that comes to mind [...]

Comment on lines +41 to +42
(assert (apply distinct? ids)
"Members should have unique ids"))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will check what can be done. At the very least a pr-str ids is due

(try
(sut/apply-overrides members overrides)
(catch AssertionError _
::EXCEPTION))))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's an are test case that will expect this ::EXCEPTION, so these aren't omitted

(that's part of why I put these in caps - to make it more immediately visible that something is both declared and consumed)

@vemv vemv mentioned this pull request Feb 23, 2021
26 tasks
@vemv
Copy link
Contributor Author

vemv commented Sep 17, 2021

Let's try the alternative approach that is in the oven 👀

@vemv vemv closed this Sep 17, 2021
@thumbnail thumbnail deleted the 134-deep-merge branch October 8, 2021 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants