diff --git a/src/common/ast.odin b/src/common/ast.odin index dbc5647..3fde304 100644 --- a/src/common/ast.odin +++ b/src/common/ast.odin @@ -4,6 +4,7 @@ import "core:fmt" import "core:log" import "core:mem" import "core:odin/ast" +import "core:odin/parser" import path "core:path/slashpath" import "core:strings" @@ -69,16 +70,15 @@ keyword_map: map[string]bool = { } GlobalExpr :: struct { - name: string, - name_expr: ^ast.Expr, - expr: ^ast.Expr, - mutable: bool, - docs: ^ast.Comment_Group, - attributes: []^ast.Attribute, - deprecated: bool, - file_private: bool, - package_private: bool, - builtin: bool, + name: string, + name_expr: ^ast.Expr, + expr: ^ast.Expr, + mutable: bool, + docs: ^ast.Comment_Group, + attributes: []^ast.Attribute, + deprecated: bool, + private: parser.Private_Flag, + builtin: bool, } get_attribute_objc_type :: proc(attributes: []^ast.Attribute) -> ^ast.Expr { @@ -240,116 +240,92 @@ is_expr_basic_lit :: proc(expr: ^ast.Expr) -> bool { return ok } -collect_value_decl :: proc(exprs: ^[dynamic]GlobalExpr, file: ast.File, stmt: ^ast.Node, skip_private: bool) { - if value_decl, ok := stmt.derived.(^ast.Value_Decl); ok { - is_deprecated := false - is_private_file := false - is_private_pkg := false - is_builtin := false - - for attribute in value_decl.attributes { - for elem in attribute.elems { - if value, ok := elem.derived.(^ast.Field_Value); ok { - if ident, ok := value.field.derived.(^ast.Ident); ok { - switch ident.name { - case "private": - if val, ok := value.value.derived.(^ast.Basic_Lit); ok { - switch val.tok.text { - case "\"file\"": - is_private_file = true - case "package": - is_private_pkg = true - } - } else { - is_private_pkg = true - } - } - } - } else if ident, ok := elem.derived.(^ast.Ident); ok { - switch ident.name { - case "deprecated": - is_deprecated = true - case "builtin": - is_builtin = true - case "private": - is_private_pkg = true - } - } - } - } +collect_value_decl :: proc( + exprs: ^[dynamic]GlobalExpr, + file: ast.File, + file_tags: parser.File_Tags, + stmt: ^ast.Node, + skip_private: bool, +) { + value_decl, is_value_decl := stmt.derived.(^ast.Value_Decl) - if is_private_file && skip_private { - return - } + if !is_value_decl { + return + } - // If a private status is not explicitly set with an attribute above the declaration - // check the file comment. - if !is_private_file && !is_private_pkg && file.docs != nil { - for comment in file.docs.list { - txt := comment.text - if strings.has_prefix(txt, "//+private") { - txt = strings.trim_prefix(txt, "//+private") - is_private_pkg = true - - if strings.has_prefix(txt, " ") { - txt = strings.trim_space(txt) - if txt == "file" { - is_private_file = true - } + global_expr := GlobalExpr{ + mutable = value_decl.is_mutable, + docs = value_decl.docs, + attributes = value_decl.attributes[:], + private = file_tags.private, + } + + for attribute in value_decl.attributes { + for elem in attribute.elems { + ident: ^ast.Ident + value: ast.Any_Node + + #partial switch v in elem.derived { + case ^ast.Field_Value: + ident = v.field.derived.(^ast.Ident) or_continue + value = v.value.derived + case ^ast.Ident: + ident = v + case: + continue + } + + switch ident.name { + case "deprecated": + global_expr.deprecated = true + case "builtin": + global_expr.builtin = true + case "private": + if val, ok := value.(^ast.Basic_Lit); ok { + switch val.tok.text { + case "\"file\"": + global_expr.private = .File + case "\"package\"": + global_expr.private = .Package } - } else if strings.has_prefix(txt, "//+build ignore") { - is_private_pkg = true - is_private_file = true + } else { + global_expr.private = .Package } } } + } - for name, i in value_decl.names { - str := get_ast_node_string(name, file.src) - - if value_decl.type != nil { - append( - exprs, - GlobalExpr { - name = str, - name_expr = name, - expr = value_decl.type, - mutable = value_decl.is_mutable, - docs = value_decl.docs, - attributes = value_decl.attributes[:], - deprecated = is_deprecated, - builtin = is_builtin, - package_private = is_private_pkg, - }, - ) - } else { - if len(value_decl.values) > i { - append( - exprs, - GlobalExpr { - name = str, - name_expr = name, - expr = value_decl.values[i], - mutable = value_decl.is_mutable, - docs = value_decl.docs, - attributes = value_decl.attributes[:], - deprecated = is_deprecated, - builtin = is_builtin, - package_private = is_private_pkg, - }, - ) - } - } + if file_tags.ignore { + global_expr.private = .File + } + + if skip_private && global_expr.private == .File { + return + } + + for name, i in value_decl.names { + global_expr.name = get_ast_node_string(name, file.src) + global_expr.name_expr = name + + if value_decl.type != nil { + global_expr.expr = value_decl.type + append(exprs, global_expr) + } else if len(value_decl.values) > i { + global_expr.expr = value_decl.values[i] + append(exprs, global_expr) } } } collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr { exprs := make([dynamic]GlobalExpr, context.temp_allocator) + defer shrink(&exprs) + + file_tags := parser.parse_file_tags(file, context.temp_allocator) for decl in file.decls { if value_decl, ok := decl.derived.(^ast.Value_Decl); ok { - collect_value_decl(&exprs, file, decl, skip_private) + collect_value_decl(&exprs, file, file_tags, decl, skip_private) } else if when_decl, ok := decl.derived.(^ast.When_Stmt); ok { if when_decl.cond == nil { continue @@ -395,13 +371,13 @@ collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr { if allowed { if block, ok := when_decl.body.derived.(^ast.Block_Stmt); ok { for stmt in block.stmts { - collect_value_decl(&exprs, file, stmt, skip_private) + collect_value_decl(&exprs, file, file_tags, stmt, skip_private) } } } else if ident.name != "ODIN_OS" && ident.name != "ODIN_ARCH" { if block, ok := when_decl.body.derived.(^ast.Block_Stmt); ok { for stmt in block.stmts { - collect_value_decl(&exprs, file, stmt, skip_private) + collect_value_decl(&exprs, file, file_tags, stmt, skip_private) } } } @@ -409,7 +385,7 @@ collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr { } else { if block, ok := when_decl.body.derived.(^ast.Block_Stmt); ok { for stmt in block.stmts { - collect_value_decl(&exprs, file, stmt, skip_private) + collect_value_decl(&exprs, file, file_tags, stmt, skip_private) } } } @@ -420,7 +396,7 @@ collect_globals :: proc(file: ast.File, skip_private := false) -> []GlobalExpr { if block, ok := foreign_decl.body.derived.(^ast.Block_Stmt); ok { for stmt in block.stmts { - collect_value_decl(&exprs, file, stmt, skip_private) + collect_value_decl(&exprs, file, file_tags, stmt, skip_private) } } } diff --git a/src/server/collector.odin b/src/server/collector.odin index 26e4809..a9df6a6 100644 --- a/src/server/collector.odin +++ b/src/server/collector.odin @@ -644,11 +644,11 @@ collect_symbols :: proc(collection: ^SymbolCollection, file: ast.File, uri: stri symbol.flags |= {.Deprecated} } - if expr.file_private { + if expr.private == .File { symbol.flags |= {.PrivateFile} } - if expr.package_private { + if expr.private == .Package { symbol.flags |= {.PrivatePackage} } diff --git a/tests/completions_test.odin b/tests/completions_test.odin index ac74117..1f17fcb 100644 --- a/tests/completions_test.odin +++ b/tests/completions_test.odin @@ -2,6 +2,7 @@ package tests import "core:fmt" import "core:testing" +import "core:strings" import test "src:testing" @@ -1069,6 +1070,44 @@ ast_file_private_completion :: proc(t: ^testing.T) { test.expect_completion_details(t, &source, ".", {}) } +@(test) +ast_file_tag_private_completion :: proc(t: ^testing.T) { + comments := []string{ + "// +private", + "//+private file", + "// +build ignore", + } + + for comment in comments { + + b := strings.builder_make(context.temp_allocator) + + strings.write_string(&b, comment) + strings.write_string(&b, ` + package my_package + + my_proc :: proc() -> bool {} + `) + + source := test.Source { + main = `package main + import "my_package" + main :: proc() { + my_package.{*} + } + `, + packages = { + { + pkg = "my_package", + source = strings.to_string(b), + } + }, + } + + test.expect_completion_details(t, &source, ".", {}) + } +} + @(test) ast_non_mutable_variable_struct_completion :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator)