Skip to content

Commit 05614d0

Browse files
committed
feat(prettier): use npx for project-local prettier
If npx is available, use it in preference to calling prettier directly. This enables project-local prettier, which is important for teams to use matching versions. This doesn't change anything for non-project prettier installs, because npx will fall back to global prettier. Since the existence of npx doesn't actually imply the existence of prettier, we have to call it once to find out if it's usable. Since this is expensive (compared to not calling it, anyway), cache the result as s:prettier_is_available and invalidate if prettier_executable changes.
1 parent 4209391 commit 05614d0

File tree

4 files changed

+61
-16
lines changed

4 files changed

+61
-16
lines changed

autoload/codefmt/prettier.vim

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ let s:supported_filetypes = ['javascript', 'markdown', 'html', 'css', 'yaml',
2020
\ 'jsx', 'less', 'scss', 'mdx', 'vue']
2121

2222

23+
""
24+
" @private
25+
" Invalidates the cached prettier availability detection.
26+
function! codefmt#prettier#InvalidateIsAvailable() abort
27+
unlet! s:prettier_is_available
28+
endfunction
29+
30+
2331
""
2432
" @private
2533
" Formatter: prettier
@@ -30,9 +38,21 @@ function! codefmt#prettier#GetFormatter() abort
3038
\ 'and configure the prettier_executable flag'}
3139

3240
function l:formatter.IsAvailable() abort
33-
let l:cmd = codefmt#formatterhelpers#ResolveFlagToArray(
34-
\ 'prettier_executable')
35-
return !empty(l:cmd) && executable(l:cmd[0])
41+
if !exists('s:prettier_is_available')
42+
let s:prettier_is_available = 0
43+
let l:cmd = codefmt#formatterhelpers#ResolveFlagToArray(
44+
\ 'prettier_executable')
45+
if !empty(l:cmd) && executable(l:cmd[0])
46+
" Unfortunately the availability of npx isn't enough to tell whether
47+
" prettier is available, and npx doesn't have a way of telling us.
48+
" Fetching the prettier version should suffice.
49+
let l:result = maktaba#syscall#Create(l:cmd + ['--version']).Call(0)
50+
if v:shell_error == 0
51+
let s:prettier_is_available = 1
52+
endif
53+
endif
54+
endif
55+
return s:prettier_is_available
3656
endfunction
3757

3858
function l:formatter.AppliesToBuffer() abort
@@ -52,7 +72,7 @@ function! codefmt#prettier#GetFormatter() abort
5272
if @% == ""
5373
call extend(l:cmd, ['--parser', 'babylon'])
5474
else
55-
call extend(l:cmd, ['--stdin-filepath', @%])
75+
call extend(l:cmd, ['--stdin-filepath', expand('%:p')])
5676
endif
5777

5878
call maktaba#ensure#IsNumber(a:startline)
@@ -71,7 +91,13 @@ function! codefmt#prettier#GetFormatter() abort
7191
\ 'prettier_options'))
7292

7393
try
74-
let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call()
94+
let l:syscall = maktaba#syscall#Create(l:cmd).WithStdin(l:input)
95+
if isdirectory(expand('%:p:h'))
96+
" Change to the containing directory so that npx will find
97+
" a project-local prettier in node_modules
98+
let l:syscall = l:syscall.WithCwd(expand('%:p:h'))
99+
endif
100+
let l:result = l:syscall.Call()
75101
let l:formatted = split(l:result.stdout, "\n")
76102
call maktaba#buffer#Overwrite(1, line('$'), l:formatted)
77103
catch /ERROR(ShellError):/

doc/codefmt.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,13 @@ Default: 'shfmt' `
8383
*codefmt:prettier_options*
8484
Command line arguments to feed prettier. Either a list or callable that takes
8585
no args and returns a list with command line arguments.
86-
Default: [ '--single-quote', '--trailing-comma=all', '--arrow-parens=always',
87-
'--print-width=80'] `
86+
Default: [] `
8887

8988
*codefmt:prettier_executable*
90-
The path to the prettier executable.
91-
Default: 'prettier' `
89+
The path to the prettier executable. String, list, or callable that takes no
90+
args and returns a string or a list. The default uses npx if available, so
91+
that the repository-local prettier will have priority.
92+
Default: function('s:LookupPrettierExecutable') `
9293

9394
*codefmt:rustfmt_options*
9495
Command line arguments to feed rustfmt. Either a list or callable that takes

instant/flags.vim

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,21 @@ call s:plugin.Flag('shfmt_executable', 'shfmt')
114114
call s:plugin.Flag('prettier_options', [])
115115

116116
""
117-
" The path to the prettier executable.
118-
call s:plugin.Flag('prettier_executable', 'prettier')
117+
" @private
118+
function s:LookupPrettierExecutable() abort
119+
return executable('npx') ? ['npx', '--no-install', 'prettier'] : 'prettier'
120+
endfunction
121+
122+
""
123+
" The path to the prettier executable. String, list, or callable that
124+
" takes no args and returns a string or a list. The default uses npx if
125+
" available, so that the repository-local prettier will have priority.
126+
call s:plugin.Flag('prettier_executable', function('s:LookupPrettierExecutable'))
127+
128+
" Invalidate cache of detected prettier availability whenever
129+
" prettier_executable changes.
130+
call s:plugin.flags.prettier_executable.AddCallback(
131+
\ maktaba#function#FromExpr('codefmt#prettier#InvalidateIsAvailable()'), 0)
119132

120133
""
121134
" Command line arguments to feed rustfmt. Either a list or callable that

vroom/prettier.vroom

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ examples.
1414

1515
:call codefmt#SetWhetherToPerformIsAvailableChecksForTesting(0)
1616

17+
The default value of prettier_executable is a function that checks if npx is
18+
available. We need a deterministic result for these tests, so we'll replace that
19+
with a simple string.
20+
21+
:Glaive codefmt prettier_executable='prettier'
1722

1823
The prettier formatter expects the prettier executable to be installed on your system.
1924

2025
% function HelloWorld(){if(!greeting){return null};}
2126
:FormatCode prettier
22-
! prettier .*
27+
! cd .* prettier .*
2328
$ function HelloWorld() {
2429
$ if (!greeting) {
2530
$ return null;
@@ -31,7 +36,7 @@ prettier_executable flag if the default of "prettier" doesn't work.
3136

3237
:Glaive codefmt prettier_executable='myprettier'
3338
:FormatCode prettier
34-
! myprettier .*
39+
! cd .* myprettier .*
3540
$ function HelloWorld() {
3641
$ if (!greeting) {
3742
$ return null;
@@ -46,7 +51,7 @@ You can format any buffer with prettier specifying the formatter explicitly.
4651
% function HelloWorld(){if(!greeting){return null};}
4752

4853
:FormatCode prettier
49-
! prettier .*2>.*
54+
! cd .* prettier .*2>.*
5055
$ function HelloWorld() {
5156
$ if (!greeting) {
5257
$ return null;
@@ -65,7 +70,7 @@ Errors are reported using the quickfix list.
6570
% function foo() {
6671

6772
:FormatCode prettier
68-
! prettier .*2> (.*)
73+
! cd .* prettier .*2> (.*)
6974
$ 2 (status)
7075
$ echo >\1 ' (command)
7176
|[error] stdin: SyntaxError: Unexpected token (2:1)\n
@@ -87,7 +92,7 @@ It can format specific line ranges of code using :FormatLines.
8792
|function Greet(){if(!greeting){return null};}
8893

8994
:1,1FormatLines prettier
90-
! prettier .*--parser babylon --range-end 50.*2>.*
95+
! cd .* prettier .*--parser babylon --range-end 50.*2>.*
9196
$ function HelloWorld() {
9297
$ if (!greeting) {
9398
$ return null;

0 commit comments

Comments
 (0)