Repository: prettier/prettier (JavaScript)

Issue:
`--write` to show text identifier for unchanged files ('grey color only' an accessibility concern 🧑‍🦯)
The output of `--write` can't distinguish between changed and unchanged text if color is not visible.

### Suggestion: Distinguish using text.

Consider that the use of color to distinguish changed vs unchanged files is not the best way to convey this difference. It has these problems:
* Visually impaired users may use a two-tone terminal that doesn't output any color information. It's impossible to distinguish in such case.
* Copying the log and pasting it somewhere else that only support plain text and no formatting, will cause the user to lose the color information. It's impossible to distinguish in such case.
* Scripts that grep the output cannot distinguish the files unless they programmatically inspect the color information. The `(cached)` keyword is unreliable as it only appears when `isCacheExists` is true.
* Cloud services that only log the text output and not the color information will not be able to display to the service user which files changed and which remain unchanged.

I think while the grey color is useful to visually distinguish, to solve the above cases we should also add the keyword `(unchanged)` after the file name. See example below.

### Example demonstrating suggestion

Take two exact copies of a project with three files, only the second of which will change in formatting.

With the current version of Prettier, running `npx/yarn prettier --write .` on first copy:

```
path/to/file1.ext 1ms
path/to/file2.ext 5ms
path/to/file3.ext 0ms (cached)
```
_(can't show color but you can imagine it: file1 and file3 will be grey)_

With a fixed version of Prettier, running `npx/yarn prettier --write .` on second copy:

```
path/to/file1.ext 1ms (unchanged)
path/to/file2.ext 5ms
path/to/file3.ext 0ms (unchanged) (cached)
```
_(can't show color but you can imagine it: file1 and file3 will be grey)_

Note: Although `(cached)` only appears for cached unchanged files, I would suggest not combining the keywords to `(unchanged, cached)` because it's harder to grep for both `(unchanged)` and `(unchanged,` in the output. Also any scripts that grep for `/ \(cached\)$/` would start to fail. That's why I suggest `(unchanged) (cached)`.

### Relevant information

Relevant code:

https://github.com/prettier/prettier/blob/f7959cccbfaf38f685b660f39eed061f5eec5ec2/src/cli/format.js#L432-L441

Relevant issues:

This was kind of hinted before in #3525 but apparently misunderstood. I believe the original author there possibly couldn't see the grey vs white distinction because the terminal didn't support colors, and everything was in the same color. This may have led to the **false** impression that all files are being overwritten when in reality, all filenames were being output to log, but only changed files were overwritten. If the output included the keyword `(unchanged)` this could possibly be avoided.

Suggestion to reduce verbosity was moved to #15480.

### I can open a pull request

I believe both suggestions can be achieved with a simple change:
* On line 435, change ``}ms`;`` to ``}ms (unchanged)`;``

Just let me know if the suggestions are acceptable.
`--write` to show text identifier for unchanged files ('grey color only' an accessibility concern 🧑‍🦯)
The output of `--write` can't distinguish between changed and unchanged text if color is not visible.

### Suggestion: Distinguish using text.

Consider that the use of color to distinguish changed vs unchanged files is not the best way to convey this difference. It has these problems:
* Visually impaired users may use a two-tone terminal that doesn't output any color information. It's impossible to distinguish in such case.
* Copying the log and pasting it somewhere else that only support plain text and no formatting, will cause the user to lose the color information. It's impossible to distinguish in such case.
* Scripts that grep the output cannot distinguish the files unless they programmatically inspect the color information. The `(cached)` keyword is unreliable as it only appears when `isCacheExists` is true.
* Cloud services that only log the text output and not the color information will not be able to display to the service user which files changed and which remain unchanged.

I think while the grey color is useful to visually distinguish, to solve the above cases we should also add the keyword `(unchanged)` after the file name. See example below.

### Example demonstrating suggestion

Take two exact copies of a project with three files, only the second of which will change in formatting.

With the current version of Prettier, running `npx/yarn prettier --write .` on first copy:

```
path/to/file1.ext 1ms
path/to/file2.ext 5ms
path/to/file3.ext 0ms (cached)
```
_(can't show color but you can imagine it: file1 and file3 will be grey)_

With a fixed version of Prettier, running `npx/yarn prettier --write .` on second copy:

```
path/to/file1.ext 1ms (unchanged)
path/to/file2.ext 5ms
path/to/file3.ext 0ms (unchanged) (cached)
```
_(can't show color but you can imagine it: file1 and file3 will be grey)_

Note: Although `(cached)` only appears for cached unchanged files, I would suggest not combining the keywords to `(unchanged, cached)` because it's harder to grep for both `(unchanged)` and `(unchanged,` in the output. Also any scripts that grep for `/ \(cached\)$/` would start to fail. That's why I suggest `(unchanged) (cached)`.

### Relevant information

Relevant code:

https://github.com/prettier/prettier/blob/f7959cccbfaf38f685b660f39eed061f5eec5ec2/src/cli/format.js#L432-L441

Relevant issues:

This was kind of hinted before in #3525 but apparently misunderstood. I believe the original author there possibly couldn't see the grey vs white distinction because the terminal didn't support colors, and everything was in the same color. This may have led to the **false** impression that all files are being overwritten when in reality, all filenames were being output to log, but only changed files were overwritten. If the output included the keyword `(unchanged)` this could possibly be avoided.

Suggestion to reduce verbosity was moved to #15480.

### I can open a pull request

I believe both suggestions can be achieved with a simple change:
* On line 435, change ``}ms`;`` to ``}ms (unchanged)`;``

Just let me know if the suggestions are acceptable.


Hints:
I agree with giving `(unchanged)` to output for accessibility.
I agree with giving `(unchanged)` to output for accessibility.

Patch:
diff --git a/src/cli/format.js b/src/cli/format.js
--- a/src/cli/format.js
+++ b/src/cli/format.js
@@ -437,7 +437,7 @@ async function formatFiles(context) {
       } else if (!context.argv.check && !context.argv.listDifferent) {
         const message = `${chalk.grey(fileNameToDisplay)} ${
           Date.now() - start
-        }ms`;
+        }ms (unchanged)`;
         if (isCacheExists) {
           context.logger.log(`${message} (cached)`);
         } else {


Test patch:
diff --git a/tests/integration/__tests__/__snapshots__/line-after-filepath-with-errors.js.snap b/tests/integration/__tests__/__snapshots__/line-after-filepath-with-errors.js.snap
--- a/tests/integration/__tests__/__snapshots__/line-after-filepath-with-errors.js.snap
+++ b/tests/integration/__tests__/__snapshots__/line-after-filepath-with-errors.js.snap
@@ -83,7 +83,7 @@ invalid-2.unknown
 [[called readline.clearLine(process.stdout)]]
 valid-1.js
 [[called readline.clearLine(process.stdout)]]
-valid-1.js 0ms"
+valid-1.js 0ms (unchanged)"
 `;
 
 exports[`Line breaking after filepath with errors (write) 1`] = `[]`;
diff --git a/tests/integration/__tests__/__snapshots__/write.js.snap b/tests/integration/__tests__/__snapshots__/write.js.snap
--- a/tests/integration/__tests__/__snapshots__/write.js.snap
+++ b/tests/integration/__tests__/__snapshots__/write.js.snap
@@ -2,7 +2,7 @@
 
 exports[`do not write file with --write + formatted file (stderr) 1`] = `""`;
 
-exports[`do not write file with --write + formatted file (stdout) 1`] = `"formatted.js 0ms"`;
+exports[`do not write file with --write + formatted file (stdout) 1`] = `"formatted.js 0ms (unchanged)"`;
 
 exports[`do not write file with --write + invalid file (stderr) 1`] = `
 "[error] invalid.js: SyntaxError: Unexpected token (1:17)
diff --git a/tests/integration/__tests__/cache.js b/tests/integration/__tests__/cache.js
--- a/tests/integration/__tests__/cache.js
+++ b/tests/integration/__tests__/cache.js
@@ -133,7 +133,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -146,8 +146,8 @@ describe("--cache option", () => {
       ]);
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
-          expect.stringMatching(/^a\.js .+ms \(cached\)$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^a\.js .+ms \(unchanged\) \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -167,7 +167,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -182,7 +182,7 @@ describe("--cache option", () => {
         // the cache of `b.js` is only available.
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -202,7 +202,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -218,7 +218,7 @@ describe("--cache option", () => {
         // the cache of `b.js` is only available.
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -234,7 +234,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -250,7 +250,7 @@ describe("--cache option", () => {
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
     });
@@ -273,7 +273,7 @@ describe("--cache option", () => {
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -309,7 +309,7 @@ describe("--cache option", () => {
       expect(thirdStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -336,7 +336,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -394,7 +394,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -404,8 +404,8 @@ describe("--cache option", () => {
       );
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
-          expect.stringMatching(/^a\.js .+ms \(cached\)$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^a\.js .+ms \(unchanged\) \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -425,7 +425,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -440,7 +440,7 @@ describe("--cache option", () => {
         // the cache of `b.js` is only available.
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -460,7 +460,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -474,8 +474,8 @@ describe("--cache option", () => {
       );
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
-          expect.stringMatching(/^a\.js .+ms \(cached\)$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^a\.js .+ms \(unchanged\) \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -491,7 +491,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -507,7 +507,7 @@ describe("--cache option", () => {
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
     });
@@ -530,7 +530,7 @@ describe("--cache option", () => {
       expect(secondStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -566,7 +566,7 @@ describe("--cache option", () => {
       expect(thirdStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
         ]),
       );
     });
@@ -593,7 +593,7 @@ describe("--cache option", () => {
       expect(firstStdout.split("\n")).toEqual(
         expect.arrayContaining([
           expect.stringMatching(/^a\.js .+ms$/),
-          expect.stringMatching(/^b\.js .+ms$/),
+          expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
         ]),
       );
 
@@ -677,7 +677,7 @@ describe("--cache option", () => {
         expect(firstStdout.split("\n")).toEqual(
           expect.arrayContaining([
             expect.stringMatching(/^a\.js .+ms$/),
-            expect.stringMatching(/^b\.js .+ms$/),
+            expect.stringMatching(/^b\.js .+ms \(unchanged\)$/),
           ]),
         );
 
@@ -687,8 +687,8 @@ describe("--cache option", () => {
         );
         expect(secondStdout.split("\n")).toEqual(
           expect.arrayContaining([
-            expect.stringMatching(/^a\.js .+ms \(cached\)$/),
-            expect.stringMatching(/^b\.js .+ms \(cached\)$/),
+            expect.stringMatching(/^a\.js .+ms \(unchanged\) \(cached\)$/),
+            expect.stringMatching(/^b\.js .+ms \(unchanged\) \(cached\)$/),
           ]),
         );
       });