Skip to content

Commit e54d6a4

Browse files
committed
feat: support suggestions in pattern error messages
1 parent 12882f0 commit e54d6a4

File tree

3 files changed

+24
-4
lines changed

3 files changed

+24
-4
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export default {
4646
- Install dependencies using `pnpm install`
4747
- Run interactive tests using `pnpm dev`
4848
49+
## Acknowledgements
50+
51+
Some features in impound were inspired by [TanStack Start's import protection](https://tanstack.com/start/latest/docs/framework/react/guide/import-protection).
52+
4953
## License
5054
5155
Made with ❤️

src/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export interface ImpoundMatcherOptions {
3232
* Return `false` to allow the import and suppress the default error/warning.
3333
*/
3434
onViolation?: (info: ImpoundViolationInfo) => boolean | void
35-
/** An array of patterns to prevent being imported, along with an optional warning to display. */
36-
patterns: [importPattern: string | RegExp | ((id: string, importer: string) => boolean | string), warning?: string][]
35+
/** An array of patterns to prevent being imported, along with an optional warning and suggestions to display. */
36+
patterns: [importPattern: string | RegExp | ((id: string, importer: string) => boolean | string), warning?: string, suggestions?: string[]][]
3737
}
3838

3939
export interface ImpoundSharedOptions {
@@ -72,15 +72,18 @@ export const ImpoundPlugin = createUnplugin((globalOptions: ImpoundOptions) => {
7272

7373
const relativeImporter = isAbsolute(importer) && globalOptions.cwd ? relative(globalOptions.cwd, importer) : importer
7474
const logError = options.error === false ? console.error : this.error.bind(this)
75-
for (const [pattern, warning] of options.patterns) {
75+
for (const [pattern, warning, suggestions] of options.patterns) {
7676
const usesImport = pattern instanceof RegExp
7777
? pattern.test(id)
7878
: typeof pattern === 'string'
7979
? pattern === id
8080
: pattern(id, relativeImporter)
8181

8282
if (usesImport) {
83-
const message = `${typeof usesImport === 'string' ? usesImport : (warning || 'Invalid import')} [importing \`${id}\` from \`${relativeImporter}\`]`
83+
let message = `${typeof usesImport === 'string' ? usesImport : (warning || 'Invalid import')} [importing \`${id}\` from \`${relativeImporter}\`]`
84+
if (suggestions?.length) {
85+
message += `\n\nSuggestions:\n${suggestions.map(s => ` - ${s}`).join('\n')}`
86+
}
8487
if (options.onViolation?.({ id, importer: relativeImporter, message }) === false) {
8588
continue
8689
}

test/index.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ describe('impound plugin', () => {
118118
const result = await process(code('bar'), { patterns: [['bar', '"bar" is a dangerous library and should never be used.']] }) as RollupError
119119
expect(result.message).toMatchInlineSnapshot(`"[plugin impound] "bar" is a dangerous library and should never be used. [importing \`bar\` from \`entry.js\`]"`)
120120
})
121+
122+
it('appends suggestions to error message', async () => {
123+
const result = await process(code('bar'), {
124+
patterns: [['bar', 'Server-only import', ['Use a server function instead', 'Move this import to a .server.ts file']]],
125+
}) as RollupError
126+
expect(result.message).toMatchInlineSnapshot(`
127+
"[plugin impound] Server-only import [importing \`bar\` from \`entry.js\`]
128+
129+
Suggestions:
130+
- Use a server function instead
131+
- Move this import to a .server.ts file"
132+
`)
133+
})
121134
})
122135

123136
async function process(code: string, opts: ImpoundOptions, importer = 'entry.js') {

0 commit comments

Comments
 (0)