Skip to content

Commit 872c8d5

Browse files
authored
Show link to the docs for route segment config options (vercel#42779)
This PR adds links to the docs when hovering on the option names and values. It will be helpful especially when there's an invalid value: ![option-links](https://user-images.githubusercontent.com/3676859/201303933-8a8bcfd3-4e6b-4191-a0b0-2944539ccf66.gif) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
1 parent 4240dcc commit 872c8d5

File tree

1 file changed

+45
-19
lines changed

1 file changed

+45
-19
lines changed

packages/next/server/next-typescript.ts

+45-19
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const API_DOCS: Record<
4141
{
4242
description: string
4343
options: Record<string, string>
44+
link: string
4445
type?: string
4546
isValid?: (value: string) => boolean
4647
getHint?: (value: any) => string
@@ -59,6 +60,7 @@ const API_DOCS: Record<
5960
'"force-static"':
6061
'This forces caching of all fetches and returns empty values from `useCookies`, `useHeaders` and `useSearchParams`.',
6162
},
63+
link: 'https://beta.nextjs.org/docs/api-reference/segment-config#dynamic',
6264
},
6365
fetchCache: {
6466
description:
@@ -79,6 +81,7 @@ const API_DOCS: Record<
7981
'"force-cache"':
8082
"This lets you intentionally opt-in to all caching of data. This option forces all fetches to be cache even if the `cache: 'no-store'` option is passed to `fetch()`.",
8183
},
84+
link: 'https://beta.nextjs.org/docs/api-reference/segment-config#fetchcache',
8285
},
8386
preferredRegion: {
8487
description:
@@ -89,6 +92,7 @@ const API_DOCS: Record<
8992
'"home"': 'Prefer deploying to the Home region.',
9093
'"edge"': 'Prefer deploying to the Edge globally.',
9194
},
95+
link: 'https://beta.nextjs.org/docs/api-reference/segment-config#preferredregion',
9296
},
9397
revalidate: {
9498
description:
@@ -100,6 +104,7 @@ const API_DOCS: Record<
100104
0: 'Specifying `0` implies that this layout or page should never be static.',
101105
30: 'Set the revalidation time to `30` seconds. The value can be `0` or any positive number.',
102106
},
107+
link: 'https://beta.nextjs.org/docs/api-reference/segment-config#revalidate',
103108
isValid: (value: string) => {
104109
return value === 'false' || Number(value) >= 0
105110
},
@@ -115,6 +120,7 @@ const API_DOCS: Record<
115120
false:
116121
'Disallow rendering dynamic params that are not generated by `generateStaticParams`.',
117122
},
123+
link: 'https://beta.nextjs.org/docs/api-reference/segment-config#dynamicparams',
118124
},
119125
runtime: {
120126
description:
@@ -123,6 +129,7 @@ const API_DOCS: Record<
123129
'"nodejs"': 'Prefer the Node.js runtime.',
124130
'"experimental-edge"': 'Prefer the experimental Edge runtime.',
125131
},
132+
link: 'https://beta.nextjs.org/docs/api-reference/segment-config#runtime',
126133
},
127134
}
128135

@@ -217,8 +224,7 @@ export function createTSPlugin(modules: {
217224
messageText:
218225
'The `"use client"` directive must be put at the top of the file.',
219226
start: node.expression.getStart(),
220-
length:
221-
node.expression.getEnd() - node.expression.getStart(),
227+
length: node.expression.getWidth(),
222228
}
223229
throw e
224230
}
@@ -428,6 +434,13 @@ export function createTSPlugin(modules: {
428434
const name = declarartion.name
429435
const value = declarartion.initializer
430436

437+
const docsLink = {
438+
kind: 'text',
439+
text:
440+
`\n\nRead more about the "${entryConfig}" option: ` +
441+
API_DOCS[entryConfig].link,
442+
}
443+
431444
if (
432445
value &&
433446
value.getFullStart() <= position &&
@@ -459,28 +472,41 @@ export function createTSPlugin(modules: {
459472
API_DOCS[entryConfig].getHint?.(key) ||
460473
'',
461474
},
475+
docsLink,
462476
],
463477
}
464-
}
465-
} else {
466-
// Hovers the name of the config
467-
if (API_DOCS[entryConfig]) {
478+
} else {
479+
// Wrong value, display the docs link
468480
overriden = {
469481
kind: ts.ScriptElementKind.enumElement,
470482
kindModifiers: ts.ScriptElementKindModifier.none,
471483
textSpan: {
472-
start: name.getStart(),
473-
length: name.getWidth(),
484+
start: value.getStart(),
485+
length: value.getWidth(),
474486
},
475487
displayParts: [],
476-
documentation: [
477-
{
478-
kind: 'text',
479-
text: getAPIDescription(entryConfig),
480-
},
481-
],
488+
documentation: [docsLink],
482489
}
483490
}
491+
} else {
492+
// Hovers the name of the config
493+
494+
overriden = {
495+
kind: ts.ScriptElementKind.enumElement,
496+
kindModifiers: ts.ScriptElementKindModifier.none,
497+
textSpan: {
498+
start: name.getStart(),
499+
length: name.getWidth(),
500+
},
501+
displayParts: [],
502+
documentation: [
503+
{
504+
kind: 'text',
505+
text: getAPIDescription(entryConfig),
506+
},
507+
docsLink,
508+
],
509+
}
484510
}
485511
})
486512
if (overriden) return overriden
@@ -529,8 +555,7 @@ export function createTSPlugin(modules: {
529555
code: NEXT_TS_ERRORS.INVALID_SERVER_API,
530556
messageText: `"${name}" is not allowed in Server Components.`,
531557
start: element.name.getStart(),
532-
length:
533-
element.name.getEnd() - element.name.getStart(),
558+
length: element.name.getWidth(),
534559
})
535560
}
536561
}
@@ -556,7 +581,7 @@ export function createTSPlugin(modules: {
556581
code: NEXT_TS_ERRORS.INVALID_ENTRY_EXPORT,
557582
messageText: `"${name.text}" is not a valid Next.js entry export value.`,
558583
start: name.getStart(),
559-
length: name.getEnd() - name.getStart(),
584+
length: name.getWidth(),
560585
})
561586
} else if (API_DOCS[name.text]) {
562587
// Check if the value is valid
@@ -612,7 +637,8 @@ export function createTSPlugin(modules: {
612637
ts.isBigIntLiteral(value) ||
613638
ts.isArrayLiteralExpression(value) ||
614639
ts.isObjectLiteralExpression(value) ||
615-
ts.isRegularExpressionLiteral(value)
640+
ts.isRegularExpressionLiteral(value) ||
641+
ts.isPrefixUnaryExpression(value)
616642
) {
617643
isInvalid = true
618644
displayedValue = value.getText()
@@ -632,7 +658,7 @@ export function createTSPlugin(modules: {
632658
errorMessage ||
633659
`"${displayedValue}" is not a valid value for the "${name.text}" option.`,
634660
start: value.getStart(),
635-
length: value.getEnd() - value.getStart(),
661+
length: value.getWidth(),
636662
})
637663
}
638664
}

0 commit comments

Comments
 (0)