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

Don't format empty strings #69

Merged
merged 5 commits into from
Sep 18, 2021
Merged

Don't format empty strings #69

merged 5 commits into from
Sep 18, 2021

Conversation

kytta
Copy link
Contributor

@kytta kytta commented Sep 17, 2021

Chalk has this feature that when a string is empty, it doesn't add opening and closing modifiers to it, unlike Colorette:

> chalk.bold('')
''
> colorette.bold('')
'\x1B[1m\x1B[22m'

This makes the migration from Chalk sometimes problematic, especially in tests, which often depend on pre-generated fixtures. Apart from that, returning a self-closing ANSI sequence seems suboptimal to me.

This PR rewrites the raw() function and adds a string length check. This didn't seem to cause any slowdowns when running benchmarks, since the needed conversion has been done anyway.

@kibertoad
Copy link
Collaborator

Could you please rebase this?

index.js Outdated Show resolved Hide resolved
@kytta
Copy link
Contributor Author

kytta commented Sep 17, 2021

Could you please rebase this?

@kibertoad sure

@jorgebucaran
Copy link
Owner

Actually, couldn't we lift this check outside the body of the function? We just need to know if the string is empty or not.

@jorgebucaran
Copy link
Owner

Nevermind. I think we can't, lol. 😁

@kytta
Copy link
Contributor Author

kytta commented Sep 17, 2021

Nevermind. I think we can't, lol. 😁

Yeah, I was going to question that 😅

@jorgebucaran
Copy link
Owner

Using default function parameters might be better.

const raw =
  (open, close, searchRegex, replaceValue) =>
  (s = "") =>
    s === ""
      ? s
      : open +
        (~(s += "").indexOf(close, 4) // skip opening \x1b[
          ? s.replace(searchRegex, replaceValue)
          : s) +
        close

And add these tests to index.test.js, please:

export default [
  t("colorette", [
    t("emptiness", [
      equal(c.blue(), ""),
      equal(c.blue(""), ""),
      equal(c.blue(undefined), ""),
      equal(c.blue(0), "\x1b[34m0\x1b[39m"),
      equal(c.blue(null), "\x1b[34mnull\x1b[39m"),
      equal(c.blue(false), "\x1b[34mfalse\x1b[39m"),
    ]),
// ...

@jorgebucaran jorgebucaran added the enhancement New feature or request label Sep 17, 2021
@kytta
Copy link
Contributor Author

kytta commented Sep 18, 2021

Using default function parameters might be better.

const raw =
  (open, close, searchRegex, replaceValue) =>
  (s = "") =>
    s === ""
      ? s
      : open +
        (~(s += "").indexOf(close, 4) // skip opening \x1b[
          ? s.replace(searchRegex, replaceValue)
          : s) +
        close

This is not the perfect solution, because the default value will only be used if s is undefined. However, when the undefined is explicitly converted to a String, it becomes 'undefined'.

> String(undefined)
'undefined'

The common usage is to style a string with some content, while colour is usually enabled. That means, that the s += "" part of ~(s += "").indexOf(close, 4) will most certainly be evaluated. It doesn't really matter for the performance to bring it to the top and evaluate earlier. And since your solution adds a conditional statement anyway, I think it adds more sense to the user to actually check if String(s) (aka s += "") is empty or not.

UPDATE: As I am writing this, I have realized, that a simple check is not enough. For example, this is how Chalk behaves on empty/undefined input:

> const chalk = require('chalk')
undefined
> chalk.bold('')
''
> chalk.bold(undefined)
'\x1B[1mundefined\x1B[22m'
> chalk.bold()
''

This is harder to implement using the default values, since "no arguments" is equivalent to "argument is undefined":

> const test = (s = "") => (s += "") ? s : "STRING IS EMPTY"
undefined
> test()
'STRING IS EMPTY'
> test(undefined)
'STRING IS EMPTY'

I wonder how to implement this nicely 🤔


And add these tests to index.test.js, please:

export default [
  t("colorette", [
    t("emptiness", [
      equal(c.blue(), ""),
      equal(c.blue(""), ""),
      equal(c.blue(undefined), ""),
      equal(c.blue(0), "\x1b[34m0\x1b[39m"),
      equal(c.blue(null), "\x1b[34mnull\x1b[39m"),
      equal(c.blue(false), "\x1b[34mfalse\x1b[39m"),
    ]),
// ...

Sure, I will. I'll leave out the c.blue(undefined) for now.

@jorgebucaran
Copy link
Owner

After this change, I expect Colorette to behave as follows:

import { blue } from "colorette"

blue() //=> ""
blue("") //=> ""
blue(undefined) //=> ""
blue(0) //=> "\x1b[34m0\x1b[39m"
blue(null) //=> "\x1b[34mnull\x1b[39m"
blue(false) //=> "\x1b[34mfalse\x1b[39m"

What was your expectation?

@kytta
Copy link
Contributor Author

kytta commented Sep 18, 2021

After this change, I expect Colorette to behave as follows:

import { blue } from "colorette"

blue() //=> ""
blue("") //=> ""
blue(undefined) //=> ""
blue(0) //=> "\x1b[34m0\x1b[39m"
blue(null) //=> "\x1b[34mnull\x1b[39m"
blue(false) //=> "\x1b[34mfalse\x1b[39m"

What was your expectation?

My expectation at first was to make it behave like Chalk, so that it could be considered as a drop-in replacement:

> const { blue } = require('chalk')
> blue()
''
> blue("")
''
> blue(undefined)
'\x1B[34mundefined\x1B[39m'
> blue(0)
'\x1B[34m0\x1B[39m'
> blue(null)
'\x1B[34mnull\x1B[39m'
> blue(false)
'\x1B[34mfalse\x1B[39m'

@kibertoad
Copy link
Collaborator

kibertoad commented Sep 18, 2021

@NickKaramoff How is not returning empty string on undefined not surprising for the user?

@jorgebucaran
Copy link
Owner

My expectation at first was to make it behave like Chalk, so that it could be considered as a drop-in replacement:

We're not really a drop-in replacement. I think #69 (comment) is more sensible, so let's go with that, please.

@kibertoad
Copy link
Collaborator

@jorgebucaran I second that.

Add the default parameter so that `style(undefined)` also resolves to an empty
string.

See #69
@codecov-commenter
Copy link

codecov-commenter commented Sep 18, 2021

Codecov Report

Merging #69 (5797d96) into main (6640ac4) will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##              main       #69   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         1           
  Lines          125       129    +4     
=========================================
+ Hits           125       129    +4     
Impacted Files Coverage Δ
index.js 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6640ac4...5797d96. Read the comment docs.

@kytta
Copy link
Contributor Author

kytta commented Sep 18, 2021

@jorgebucaran all done

index.js Outdated Show resolved Hide resolved
tests/index.test.js Outdated Show resolved Hide resolved
@jorgebucaran jorgebucaran merged commit 177601f into jorgebucaran:main Sep 18, 2021
@jorgebucaran
Copy link
Owner

Now available in 2.0.1. Thank you, @NickKaramoff! 🎉

@kytta
Copy link
Contributor Author

kytta commented Sep 18, 2021

Thank you for finishing this 😅

@kytta kytta deleted the dont-format-empty branch September 18, 2021 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants