Skip to content

Commit c35345a

Browse files
authored
(fix) better error message for missing type definition (#1360)
#1352
1 parent 3167aca commit c35345a

File tree

61 files changed

+161
-116
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+161
-116
lines changed

packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -194,24 +194,25 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
194194
const start = fragment.offsetAt(fragment.getGeneratedPosition(range.start));
195195
const end = fragment.offsetAt(fragment.getGeneratedPosition(range.end));
196196
const errorCodes: number[] = context.diagnostics.map((diag) => Number(diag.code));
197-
const codeFixes = lang.getCodeFixesAtPosition(
198-
tsDoc.filePath,
199-
start,
200-
end,
201-
errorCodes,
202-
{},
203-
userPreferences
204-
);
205-
206-
const componentQuickFix = errorCodes.includes(2304) // "Cannot find name '...'."
207-
? this.getComponentImportQuickFix(start, end, lang, tsDoc.filePath, userPreferences) ??
208-
[]
209-
: [];
197+
let codeFixes = errorCodes.includes(2304) // "Cannot find name '...'."
198+
? this.getComponentImportQuickFix(start, end, lang, tsDoc.filePath, userPreferences)
199+
: undefined;
200+
codeFixes =
201+
// either-or situation
202+
codeFixes ||
203+
lang.getCodeFixesAtPosition(
204+
tsDoc.filePath,
205+
start,
206+
end,
207+
errorCodes,
208+
{},
209+
userPreferences
210+
);
210211

211212
const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
212213
docs.set(tsDoc.filePath, { fragment, snapshot: tsDoc });
213214

214-
const codeActionsPromises = codeFixes.concat(componentQuickFix).map(async (fix) => {
215+
const codeActionsPromises = codeFixes.map(async (fix) => {
215216
const documentChangesPromises = fix.changes.map(async (change) => {
216217
const { snapshot, fragment } = await docs.retrieve(change.fileName);
217218
return TextDocumentEdit.create(
@@ -311,7 +312,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
311312
lang: ts.LanguageService,
312313
filePath: string,
313314
userPreferences: ts.UserPreferences
314-
): ts.CodeFixAction[] | undefined {
315+
): readonly ts.CodeFixAction[] | undefined {
315316
const sourceFile = lang.getProgram()?.getSourceFile(filePath);
316317

317318
if (!sourceFile) {
@@ -326,7 +327,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
326327
},
327328
(node): node is ts.JsxOpeningLikeElement | ts.JsxClosingElement | ts.Identifier =>
328329
this.configManager.getConfig().svelte.useNewTransformation
329-
? ts.isNewExpression(node.parent) && ts.isIdentifier(node)
330+
? ts.isCallExpression(node.parent) &&
331+
ts.isIdentifier(node.parent.expression) &&
332+
node.parent.expression.text === '__sveltets_2_ensureComponent' &&
333+
ts.isIdentifier(node)
330334
: ts.isJsxClosingElement(node) || ts.isJsxOpeningLikeElement(node)
331335
);
332336

packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,16 @@ function isNoJsxCannotHaveMultipleAttrsError(diagnostic: Diagnostic) {
267267
* Some diagnostics have JSX-specific nomenclature. Enhance them for more clarity.
268268
*/
269269
function enhanceIfNecessary(diagnostic: Diagnostic): Diagnostic {
270-
if (diagnostic.code === DiagnosticCode.CANNOT_BE_USED_AS_JSX_COMPONENT) {
270+
if (
271+
diagnostic.code === DiagnosticCode.CANNOT_BE_USED_AS_JSX_COMPONENT ||
272+
(diagnostic.code === DiagnosticCode.TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y &&
273+
diagnostic.message.includes('Svelte2TsxComponent'))
274+
) {
271275
return {
272276
...diagnostic,
273277
message:
274278
'Type definitions are missing for this Svelte Component. ' +
275-
// eslint-disable-next-line max-len
276-
"It needs a class definition with at least the property '$$prop_def' which should contain a map of input property definitions.\n" +
277-
'Example:\n' +
278-
' class ComponentName { $$prop_def: { propertyName: string; } }\n' +
279-
'If you are using Svelte 3.31+, use SvelteComponentTyped:\n' +
279+
'If you are using Svelte 3.31+, use SvelteComponentTyped to add a definition:\n' +
280280
' import type { SvelteComponentTyped } from "svelte";\n' +
281281
' class ComponentName extends SvelteComponentTyped<{propertyName: string;}> {}\n\n' +
282282
'Underlying error:\n' +
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[
2+
{
3+
"range": {
4+
"start": { "line": 13, "character": 1 },
5+
"end": { "line": 13, "character": 11 }
6+
},
7+
"severity": 1,
8+
"source": "ts",
9+
"message": "Type definitions are missing for this Svelte Component. If you are using Svelte 3.31+, use SvelteComponentTyped to add a definition:\n import type { SvelteComponentTyped } from \"svelte\";\n class ComponentName extends SvelteComponentTyped<{propertyName: string;}> {}\n\nUnderlying error:\n'DoesntWork' cannot be used as a JSX component.\n Its instance type 'DoesntWork' is not a valid JSX element.\n Property '$$prop_def' is missing in type 'DoesntWork' but required in type 'ElementClass'.",
10+
"code": 2786,
11+
"tags": []
12+
}
13+
]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[
2+
{
3+
"range": {
4+
"start": { "line": 13, "character": 1 },
5+
"end": { "line": 13, "character": 11 }
6+
},
7+
"severity": 1,
8+
"source": "ts",
9+
"message": "Type definitions are missing for this Svelte Component. If you are using Svelte 3.31+, use SvelteComponentTyped to add a definition:\n import type { SvelteComponentTyped } from \"svelte\";\n class ComponentName extends SvelteComponentTyped<{propertyName: string;}> {}\n\nUnderlying error:\nArgument of type 'typeof DoesntWork' is not assignable to parameter of type 'new (args: { target: any; props?: any; }) => Svelte2TsxComponent<any, any, any>'.\n Type 'DoesntWork' is missing the following properties from type 'Svelte2TsxComponent<any, any, any>': $$prop_def, $$events_def, $$slot_def, $on, and 5 more.",
10+
"code": 2345,
11+
"tags": []
12+
}
13+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>hi</p>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script lang="ts">
2+
import { SvelteComponentTyped } from "svelte";
3+
import Imported from './imported.svelte';
4+
5+
class Works extends SvelteComponentTyped<any, any, any> {}
6+
class DoesntWork {}
7+
</script>
8+
9+
<!-- valid -->
10+
<Works />
11+
<Imported />
12+
13+
<!-- invalid -->
14+
<DoesntWork />

packages/svelte2tsx/src/htmlxtojsx_v2/nodes/InlineComponent.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,32 @@ export class InlineComponent {
7272
this.addNameConstDeclaration = () =>
7373
(this.startTransformation[0] = `{ const ${this._name} = __sveltets_2_createComponentAny({`);
7474
this.startEndTransformation.push('});');
75-
} else if (this.node.name === 'svelte:component') {
76-
this._name = '$$_sveltecomponent' + this.computeDepth();
77-
this.startTransformation.push(
78-
`{ const ${this._name}_ = new `,
79-
[this.node.expression.start, this.node.expression.end],
80-
'({ target: __sveltets_2_any(), props: {'
81-
);
75+
} else {
76+
const isSvelteComponentTag = this.node.name === 'svelte:component';
8277
// We don't know if the thing we use to create the Svelte component with
8378
// is actually a proper Svelte component, which would lead to errors
8479
// when accessing things like $$prop_def. Therefore widen the type
8580
// here, falling back to a any-typed component to ensure the user doesn't
8681
// get weird follup-errors all over the place. The diagnostic error
87-
// should still error on the new X() part.
88-
this.startEndTransformation.push(`}});${this._name}_;`);
89-
this.addNameConstDeclaration = () =>
90-
(this.startEndTransformation[0] = `}});const ${this._name} = __sveltets_2_typeAsComponent(${this._name}_);`);
91-
} else {
92-
this._name = '$$_' + this.node.name + this.computeDepth();
93-
const nodeNameStart = this.str.original.indexOf(this.node.name, this.node.start);
82+
// will be on the __sveltets_2_ensureComponent part, giving a more helpful message
83+
this._name =
84+
'$$_' +
85+
(isSvelteComponentTag ? 'sveltecomponent' : this.node.name) +
86+
this.computeDepth();
87+
const constructorName = this._name + 'C';
88+
const nodeNameStart = isSvelteComponentTag
89+
? this.node.expression.start
90+
: this.str.original.indexOf(this.node.name, this.node.start);
91+
const nodeNameEnd = isSvelteComponentTag
92+
? this.node.expression.end
93+
: nodeNameStart + this.node.name.length;
9494
this.startTransformation.push(
95-
'{ new ',
96-
[nodeNameStart, nodeNameStart + this.node.name.length],
97-
'({ target: __sveltets_2_any(), props: {'
95+
`{ const ${constructorName} = __sveltets_2_ensureComponent(`,
96+
[nodeNameStart, nodeNameEnd],
97+
`); new ${constructorName}({ target: __sveltets_2_any(), props: {`
9898
);
9999
this.addNameConstDeclaration = () =>
100-
(this.startTransformation[0] = `{ const ${this._name} = new `);
100+
(this.startTransformation[2] = `); const ${this._name} = new ${constructorName}({ target: __sveltets_2_any(), props: {`);
101101
this.startEndTransformation.push('}});');
102102
}
103103
}

0 commit comments

Comments
 (0)