Skip to content

Commit 6870b64

Browse files
committed
feat(parser): add TS1363 error code (#17609)
TS Playground: https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBDAnmApnAYhCARFAzAQwFcAbGAGjgG84AhAqS+gLzgF848oIQ4AiEBAAmpFHwDcQA found in `tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/import-type-declaration-error/input.ts` The span is not correct, but I think this is still better then nothing :) The span should be only on the specifiers, not on the complete import declaration.
1 parent 1c07942 commit 6870b64

9 files changed

Lines changed: 86 additions & 48 deletions

File tree

crates/oxc_linter/src/rules/import/no_empty_named_blocks.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ fn test() {
9898
"import Default, { mod } from 'mod'",
9999
"import { Named } from 'mod'",
100100
"import type { Named } from 'mod'",
101-
"import type Default, { Named } from 'mod'",
101+
// "import type Default, { Named } from 'mod'", ts error 1363
102+
"import type Default from 'mod'",
102103
"import type * as Namespace from 'mod'",
103104
"import * as Namespace from 'mod'",
104105
r#"

crates/oxc_linter/src/rules/typescript/no_import_type_side_effects.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn test() {
148148
"import { type T, U } from 'mod';",
149149
"import { T, type U } from 'mod';",
150150
"import type T from 'mod';",
151-
"import type T, { U } from 'mod';",
151+
// "import type T, { U } from 'mod';", ts error 1363
152152
"import T, { type U } from 'mod';",
153153
"import type * as T from 'mod';",
154154
"import 'mod';",

crates/oxc_linter/src/rules/unicorn/require_module_specifiers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ fn test() {
161161
r#"import {foo} from "foo""#,
162162
r#"import foo,{bar} from "foo""#,
163163
r#"import type foo from "foo""#,
164-
r#"import type foo,{bar} from "foo""#,
164+
// r#"import type foo,{bar} from "foo""#, ts error 1363
165165
r#"import foo,{type bar} from "foo""#,
166166
"const foo = 1;
167167
export {foo};",

crates/oxc_parser/src/diagnostics.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,16 @@ pub fn implements_clause_already_seen(span: Span, seen_span: Span) -> OxcDiagnos
343343
.with_help("Merge the two 'implements' clauses into one by a ','")
344344
}
345345

346+
// A type-only import can specify a default import or named bindings, but not both. ts(1363)
347+
#[cold]
348+
pub fn type_only_import_default_and_named(specifier_span: Span) -> OxcDiagnostic {
349+
ts_error(
350+
"1363",
351+
"A type-only import can specify a default import or named bindings, but not both.",
352+
)
353+
.with_label(specifier_span)
354+
}
355+
346356
#[cold]
347357
pub fn binding_rest_element_last(span: Span) -> OxcDiagnostic {
348358
OxcDiagnostic::error("A rest element must be last in a destructuring pattern").with_label(span)

crates/oxc_parser/src/js/module.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,17 +1274,6 @@ mod test {
12741274
assert_eq!(specifiers[0].name(), "defer");
12751275
});
12761276

1277-
let src = "import type foo, { bar } from 'bar';";
1278-
parse_and_assert_import_declarations(src, |declarations| {
1279-
assert_eq!(declarations.len(), 1);
1280-
let decl = declarations[0];
1281-
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
1282-
let specifiers = decl.specifiers.as_ref().unwrap();
1283-
assert_eq!(specifiers.len(), 2);
1284-
assert_eq!(specifiers[0].name(), "foo");
1285-
assert_eq!(specifiers[1].name(), "bar");
1286-
});
1287-
12881277
let src = "import foo = bar";
12891278
parse_and_assert_statements(src, |statements| {
12901279
if let Statement::TSImportEqualsDeclaration(decl) = statements[0] {

crates/oxc_parser/src/module_record.rs

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,71 @@ impl<'a> ModuleRecordBuilder<'a> {
3737
pub fn errors(&self) -> std::vec::Vec<OxcDiagnostic> {
3838
let mut errors = vec![];
3939

40+
let module_record = &self.module_record;
41+
4042
// Skip checking for exports in TypeScript
4143
if self.source_type.is_typescript() {
42-
return errors;
43-
}
44-
45-
let module_record = &self.module_record;
44+
// TS1363: A type-only import can specify a default import or named bindings, but not both.
45+
// Group import entries by statement and check only those statements that are type-only imports.
46+
if !module_record.import_entries.is_empty() {
47+
// Build map of type-only import statement spans -> (has_default, has_named).
48+
// `requested_modules` contains entries for both imports and exports, so filter is_import && is_type.
49+
let mut seen: rustc_hash::FxHashMap<Span, (bool, bool)> =
50+
rustc_hash::FxHashMap::default();
51+
for requests in module_record.requested_modules.values() {
52+
for req in requests {
53+
if req.is_import && req.is_type {
54+
seen.entry(req.statement_span).or_insert((false, false));
55+
}
56+
}
57+
}
58+
if !seen.is_empty() {
59+
for entry in &module_record.import_entries {
60+
if let Some(lookup) = seen.get_mut(&entry.statement_span) {
61+
match &entry.import_name {
62+
ImportImportName::Default(_) => lookup.0 = true,
63+
ImportImportName::Name(_) | ImportImportName::NamespaceObject => {
64+
lookup.1 = true;
65+
}
66+
}
67+
}
68+
}
69+
for (stmt_span, (has_default, has_named)) in seen {
70+
if has_default && has_named {
71+
errors.push(diagnostics::type_only_import_default_and_named(stmt_span));
72+
}
73+
}
74+
}
75+
}
76+
} else {
77+
// It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries.
78+
for name_span in &self.exported_bindings_duplicated {
79+
let old_span = module_record.exported_bindings[&name_span.name];
80+
errors.push(diagnostics::duplicate_export(
81+
&name_span.name,
82+
name_span.span,
83+
old_span,
84+
));
85+
}
4686

47-
// It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries.
48-
for name_span in &self.exported_bindings_duplicated {
49-
let old_span = module_record.exported_bindings[&name_span.name];
50-
errors.push(diagnostics::duplicate_export(&name_span.name, name_span.span, old_span));
87+
// Multiple default exports
88+
// `export default foo`
89+
// `export { default }`
90+
let default_exports = module_record
91+
.local_export_entries
92+
.iter()
93+
.filter_map(|export_entry| export_entry.export_name.default_export_span())
94+
.chain(
95+
module_record
96+
.indirect_export_entries
97+
.iter()
98+
.filter_map(|export_entry| export_entry.export_name.default_export_span()),
99+
);
100+
if default_exports.clone().count() > 1 {
101+
errors.push(diagnostics::duplicate_default_export(default_exports));
102+
}
51103
}
52104

53-
// Multiple default exports
54-
// `export default foo`
55-
// `export { default }`
56-
let default_exports = module_record
57-
.local_export_entries
58-
.iter()
59-
.filter_map(|export_entry| export_entry.export_name.default_export_span())
60-
.chain(
61-
module_record
62-
.indirect_export_entries
63-
.iter()
64-
.filter_map(|export_entry| export_entry.export_name.default_export_span()),
65-
);
66-
if default_exports.clone().count() > 1 {
67-
errors.push(diagnostics::duplicate_default_export(default_exports));
68-
}
69105
errors
70106
}
71107

tasks/coverage/snapshots/parser_babel.snap

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ commit: 761c2509
33
parser_babel Summary:
44
AST Parsed : 2223/2235 (99.46%)
55
Positive Passed: 2203/2235 (98.57%)
6-
Negative Passed: 1646/1696 (97.05%)
6+
Negative Passed: 1647/1696 (97.11%)
77
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-2/input.js
88

99
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/async-explicit-resource-management/invalid-script-top-level-using-binding/input.js
@@ -90,8 +90,6 @@ Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/ty
9090

9191
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/const-type-parameters-invalid/input.ts
9292

93-
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/import-type-declaration-error/input.ts
94-
9593
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/invalid-import-type-options-escaped-with/input.ts
9694

9795
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/invalid-import-type-options-string-with/input.ts
@@ -14290,6 +14288,12 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1429014288
· ─────
1429114289
╰────
1429214290

14291+
× TS(1363): A type-only import can specify a default import or named bindings, but not both.
14292+
╭─[babel/packages/babel-parser/test/fixtures/typescript/types/import-type-declaration-error/input.ts:1:1]
14293+
1 │ import type FooDefault, { Bar, Baz } from "module";
14294+
· ───────────────────────────────────────────────────
14295+
╰────
14296+
1429314297
× TS(1141): String literal expected.
1429414298
╭─[babel/packages/babel-parser/test/fixtures/typescript/types/import-type-dynamic-errors/input.ts:1:17]
1429514299
1 │ type X = import(3);

tasks/coverage/snapshots/parser_typescript.snap

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19906,12 +19906,10 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
1990619906
· ╰── Opened here
1990719907
╰────
1990819908

19909-
× Expected `from` but found `Identifier`
19910-
╭─[typescript/tests/cases/conformance/externalModules/typeOnly/grammarErrors.ts:1:13]
19911-
1 │ import type A from './a';
19912-
· ┬
19913-
· ╰── `from` expected
19914-
2 │ export type { A };
19909+
× TS(1363): A type-only import can specify a default import or named bindings, but not both.
19910+
╭─[typescript/tests/cases/conformance/externalModules/typeOnly/grammarErrors.ts:1:1]
19911+
1 │ import type A, { B, C } from './a';
19912+
· ───────────────────────────────────
1991519913
╰────
1991619914

1991719915
× Expected `,` or `}` but found `as`

tasks/track_memory_allocations/allocs_parser.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ File | File size || Sys allocs | Sys reallocs |
22
-------------------------------------------------------------------------------------------------------------------------------------------
33
checker.ts | 2.92 MB || 9672 | 21 || 267681 | 22847
44

5-
cal.com.tsx | 1.06 MB || 1083 | 49 || 138162 | 13699
5+
cal.com.tsx | 1.06 MB || 1091 | 49 || 138162 | 13699
66

77
RadixUIAdoptionSection.jsx | 2.52 kB || 1 | 0 || 365 | 66
88

0 commit comments

Comments
 (0)