default import and export

This commit is contained in:
Dmitry Vasilev
2023-06-05 12:08:20 +03:00
parent a3b98d198d
commit 34639b0ae3
6 changed files with 142 additions and 80 deletions

View File

@@ -41,34 +41,12 @@ export const map_destructuring_identifiers = (node, mapper) => {
} }
export const collect_imports = module => { export const collect_imports = module => {
const import_statements = return uniq(
module.stmts module.stmts
.filter(n => n.type == 'import') .filter(n => n.type == 'import')
.filter(n => !n.is_external) .filter(n => !n.is_external)
.map(n => n.full_import_path)
const imported_modules = uniq(
import_statements.map(n => n.full_import_path)
) )
const imports =
import_statements
.map(n =>
n.imports.map(i =>
({name: i.value, module: n.full_import_path})
)
)
.flat()
const by_module = Object.fromEntries(imported_modules.map(m =>
[
m,
imports
.filter(i => i.module == m)
.map(i => i.name)
]
))
return by_module
} }
export const collect_external_imports = modules => export const collect_external_imports = modules =>

View File

@@ -1,12 +1,11 @@
import {map_object, filter_object, collect_nodes_with_parents, uniq} import {map_object, map_find, filter_object, collect_nodes_with_parents, uniq}
from './utils.js' from './utils.js'
import { import {
is_eq, is_child, ancestry, ancestry_inc, map_tree, is_eq, is_child, ancestry, ancestry_inc, map_tree,
find_leaf, find_fn_by_location, find_node, find_error_origin_node, find_leaf, find_fn_by_location, find_node, find_error_origin_node,
collect_external_imports collect_external_imports, collect_destructuring_identifiers
} from './ast_utils.js' } from './ast_utils.js'
import {load_modules} from './parse_js.js' import {load_modules} from './parse_js.js'
import {find_export} from './find_definitions.js'
import {eval_modules} from './eval.js' import {eval_modules} from './eval.js'
import { import {
root_calltree_node, root_calltree_module, make_calltree, root_calltree_node, root_calltree_module, make_calltree,
@@ -555,7 +554,17 @@ const goto_definition = (state, index) => {
} else { } else {
let loc let loc
if(d.module != null) { if(d.module != null) {
const exp = find_export(node.value, state.parse_result.modules[d.module]) const exp = map_find(state.parse_result.modules[d.module].stmts, n => {
if(n.type != 'export') {
return null
}
if(n.is_default && d.is_default) {
return n.children[0]
} else if(!n.is_default && !d.is_default) {
const ids = collect_destructuring_identifiers(n.binding.name_node)
return ids.find(i => i.value == node.value)
}
})
loc = {module: d.module, index: exp.index} loc = {module: d.module, index: exp.index}
} else { } else {
loc = {module: state.current_module, index: d.index} loc = {module: state.current_module, index: d.index}

View File

@@ -261,18 +261,25 @@ ${JSON.stringify(errormessage)}, true)`
} else if(node.type == 'destructuring_pair') { } else if(node.type == 'destructuring_pair') {
return do_codegen(node.key) + ' : ' + do_codegen(node.value); return do_codegen(node.key) + ' : ' + do_codegen(node.value);
} else if(node.type == 'import') { } else if(node.type == 'import') {
const names = node.imports.map(n => n.value) let names, def
if(names.length == 0) { if(node.default_import != null) {
return '' def = `default: ${node.default_import},`
names = node.children.slice(1).map(n => n.value)
} else { } else {
return `const {${names.join(',')}} = __cxt.modules['${node.full_import_path}'];`; def = ''
names = node.children.map(n => n.value)
} }
return `const {${def} ${names.join(',')}} = __cxt.modules['${node.full_import_path}'];`;
} else if(node.type == 'export') { } else if(node.type == 'export') {
const identifiers = collect_destructuring_identifiers(node.binding.name_node) if(node.is_default) {
.map(i => i.value) return `__cxt.modules['${node_cxt.module}'].default = ${do_codegen(node.children[0])};`
return do_codegen(node.binding) } else {
+ const identifiers = collect_destructuring_identifiers(node.binding.name_node)
`Object.assign(__cxt.modules['${node_cxt.module}'], {${identifiers.join(',')}});` .map(i => i.value)
return do_codegen(node.binding)
+
`Object.assign(__cxt.modules['${node_cxt.module}'], {${identifiers.join(',')}});`
}
} else if(node.type == 'function_decl') { } else if(node.type == 'function_decl') {
const expr = node.children[0] const expr = node.children[0]
return `const ${expr.name} = ${codegen_function_expr(expr, node_cxt)};` return `const ${expr.name} = ${codegen_function_expr(expr, node_cxt)};`
@@ -991,12 +998,20 @@ const eval_statement = (s, scope, calls, context) => {
node: {...s, children: [node], result: {ok: node.result.ok}} node: {...s, children: [node], result: {ok: node.result.ok}}
} }
} else if(s.type == 'import') { } else if(s.type == 'import') {
const children = s.imports.map(i => ( const module = context.modules[s.full_import_path]
{...i, const children = s.children.map((imp, i) => (
result: {ok: true, value: context.modules[s.full_import_path][i.value]} {...imp,
result: {
ok: true,
value: imp.definition.is_default
? module['default']
: module[imp.value]
}
} }
)) ))
const imported_scope = Object.fromEntries(children.map(i => [i.value, i.result.value])) const imported_scope = Object.fromEntries(
children.map(imp => [imp.value, imp.result.value])
)
return { return {
ok: true, ok: true,
scope: {...scope, ...imported_scope}, scope: {...scope, ...imported_scope},

View File

@@ -22,7 +22,7 @@ const map_find_definitions = (nodes, mapper) => {
const scope_from_node = n => { const scope_from_node = n => {
if(n.type == 'import') { if(n.type == 'import') {
return Object.fromEntries( return Object.fromEntries(
n.imports.map(i => [i.value, i]) n.children.map(i => [i.value, i])
) )
} else if(n.type == 'export'){ } else if(n.type == 'export'){
return scope_from_node(n.binding) return scope_from_node(n.binding)
@@ -137,7 +137,13 @@ export const find_definitions = (ast, scope = {}, closure_scope = {}, module_nam
let children, full_import_path let children, full_import_path
if(ast.type == 'import') { if(ast.type == 'import') {
full_import_path = concat_path(module_name, ast.module) full_import_path = concat_path(module_name, ast.module)
children = ast.children.map(c => ({...c, definition: {module: full_import_path}})) children = ast.children.map((c, i) => ({
...c,
definition: {
module: full_import_path,
is_default: i == 0 && ast.default_import != null,
}
}))
} else if(ast.type == 'const') { } else if(ast.type == 'const') {
children = [add_trivial_definition(ast.name_node), ...ast.children.slice(1)] children = [add_trivial_definition(ast.name_node), ...ast.children.slice(1)]
} else if(ast.type == 'let') { } else if(ast.type == 'let') {
@@ -162,16 +168,6 @@ export const find_definitions = (ast, scope = {}, closure_scope = {}, module_nam
} }
} }
export const find_export = (name, module) => {
return map_find(module.stmts, n => {
if(n.type != 'export') {
return null
}
const ids = collect_destructuring_identifiers(n.binding.name_node)
return ids.find(i => i.value == name)
})
}
const BASE = 'dummy://dummy/' const BASE = 'dummy://dummy/'
const concat_path = (base, i) => { const concat_path = (base, i) => {
const result = new URL(i, BASE + base).toString() const result = new URL(i, BASE + base).toString()
@@ -184,11 +180,9 @@ const concat_path = (base, i) => {
export const topsort_modules = (modules) => { export const topsort_modules = (modules) => {
const sort_module_deps = (module) => { const sort_module_deps = (module) => {
return Object.keys(collect_imports(modules[module])) return collect_imports(modules[module])
.reduce( .map(m => sort_module_deps(m))
(result, m) => result.concat(sort_module_deps(m)), .flat()
[]
)
.concat(module) .concat(module)
} }
@@ -235,6 +229,8 @@ export const check_imports = modules => {
.reduce( .reduce(
(imports, n) => [ (imports, n) => [
...imports, ...imports,
// TODO imports
// TODO use flatmap
...(n.imports.map(i => ({name: i.value, from: n.module}))) ...(n.imports.map(i => ({name: i.value, from: n.module})))
], ],
[] []
@@ -245,7 +241,8 @@ export const check_imports = modules => {
.reduce((all, current) => [...all, ...current], []) .reduce((all, current) => [...all, ...current], [])
return {imports, exports} return {imports, exports}
//TODO check for each import, there is export // TODO check for each import, there is export. For default import there is
// default export
}) })
// Topological sort // Topological sort
// For each module // For each module

View File

@@ -1226,20 +1226,34 @@ const import_statement =
optional(by_type('pragma_external')), optional(by_type('pragma_external')),
seq([ seq([
literal('import'), literal('import'),
// TODO import can have both named import and default import,
// like 'import foo, {bar} from "module"'
optional( optional(
seq([ seq_select(0, [
list( either(
['{', '}'], list(
['{', '}'],
identifier,
),
identifier, identifier,
), ),
literal('from'), literal('from'),
]) ]),
), ),
string_literal string_literal
]) ])
]), ]),
({value: [pragma_external, imp]}) => { ({value: [pragma_external, imp]}) => {
const {value: [_import, identifiers, module], ...node} = imp const {value: [_import, imports, module], ...node} = imp
let default_import, children
if(imports == null) {
children = []
} else if(imports.value.type == 'identifier') {
default_import = imports.value.value
children = [imports.value]
} else {
children = imports.value.value
}
// remove quotes // remove quotes
const module_string = module.value.slice(1, module.value.length - 1) const module_string = module.value.slice(1, module.value.length - 1)
// if url starts with protocol, then it is always external // if url starts with protocol, then it is always external
@@ -1252,25 +1266,44 @@ const import_statement =
// TODO refactor hanlding of string literals. Drop quotes from value and // TODO refactor hanlding of string literals. Drop quotes from value and
// fix codegen for string_literal // fix codegen for string_literal
module: module_string, module: module_string,
children: identifiers == null ? [] : identifiers.value[0].value, default_import,
children,
} }
} }
) )
const export_statement = const export_statement =
if_ok( either(
seq_select(1, [ if_ok(
literal('export'), seq_select(1, [
// TODO export let statement, export default, etc. Or not allow literal('export'),
// let statement because export let cannot be compiled properly? // TODO export let statement, export default, etc. Or not allow
const_statement, // let statement because export let cannot be compiled properly?
]), const_statement,
({value, ...node}) => ({ ]),
...node, ({value, ...node}) => ({
not_evaluatable: true, ...node,
type: 'export', not_evaluatable: true,
children: [value], type: 'export',
}) is_default: false,
children: [value],
})
),
if_ok(
seq_select(2, [
literal('export'),
literal('default'),
expr,
]),
({value, ...node}) => ({
...node,
not_evaluatable: true,
type: 'export',
is_default: true,
children: [value],
})
),
) )
@@ -1479,7 +1512,6 @@ const update_children_not_rec = (node, children = node.children) => {
} }
} else if(node.type == 'import') { } else if(node.type == 'import') {
return {...node, return {...node,
imports: children,
is_statement: true, is_statement: true,
} }
} else if(node.type == 'export') { } else if(node.type == 'export') {
@@ -1685,7 +1717,7 @@ const do_load_modules = (module_names, loader, already_loaded) => {
const deps = uniq( const deps = uniq(
Object.values(modules) Object.values(modules)
.map(m => Object.keys(collect_imports(m))) .map(m => collect_imports(m))
.flat() .flat()
) )

View File

@@ -1145,6 +1145,37 @@ export const tests = [
assert_equal(state.parse_result.ok, true) assert_equal(state.parse_result.ok, true)
}), }),
test('modules default export', () => {
const modules = {
'' : "import foo from 'foo'; foo",
'foo': `export default 1`
}
assert_code_evals_to(modules , 1)
const i = test_initial_state(modules)
const s = COMMANDS.goto_definition(i, modules[''].indexOf('foo')).state
assert_equal(current_cursor_position(s), modules['foo'].indexOf('1'))
assert_equal(s.current_module, 'foo')
}),
test('modules default import', () => {
const code = `
// external
import foo from 'foo.js'
foo
`
const initial = test_initial_state(code)
const next = COMMANDS.external_imports_loaded(initial, initial, {
'foo.js': {
ok: true,
module: {
'default': 'foo_value'
},
}
})
assert_equal(active_frame(next).children.at(-1).result.value, 'foo_value')
}),
// Static analysis // Static analysis