Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Options:

- `checker_args`: Pass custom arguments to `odin check`.

- `checker_skip_packages`: Paths to packages that should not be checked by `odin check`.

- `verbose`: Logs warnings instead of just errors.

- `profile`: What profile to currently use.
Expand Down
7 changes: 7 additions & 0 deletions misc/ols.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@
"type": "string",
"description": "Pass custom arguments to `odin check`."
},
"checker_skip_packages": {
"type": "array",
"items": {
"type": "string",
},
"description": "Paths to packages that should not be checked by `odin check`"
},
"profile": {
"type": "string",
"description": "What profile to currently use."
Expand Down
1 change: 1 addition & 0 deletions src/common/config.odin
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Config :: struct {
odin_root_override: string,
checker_args: string,
checker_targets: []string,
checker_skip_packages: map[string]struct{},
client_name: string,
profile: ConfigProfile,
builtin_path: string,
Expand Down
14 changes: 9 additions & 5 deletions src/server/build.odin
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,16 @@ skip_file :: proc(filename: string) -> bool {

// Finds all packages under the provided path by walking the file system
// and appends them to the provided dynamic array
append_packages :: proc(path: string, pkgs: ^[dynamic]string, allocator := context.temp_allocator) {
append_packages :: proc(path: string, pkgs: ^[dynamic]string, skip: map[string]struct{}, allocator := context.temp_allocator) {
w := os.walker_create(path)
defer os.walker_destroy(&w)
for info in os.walker_walk(&w) {
if info.type != .Directory && filepath.ext(info.name) == ".odin" {
dir := filepath.dir(info.fullpath, allocator)
if dir in skip {
os.walker_skip_dir(&w)
continue
}
if !slice.contains(pkgs[:], dir) {
append(pkgs, dir)
}
Expand Down Expand Up @@ -208,7 +212,7 @@ try_build_package :: proc(pkg_name: string) {
p := parser.Parser {
flags = {.Optional_Semicolons},
}
if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
if !is_ols_builtin_file(fullpath) {
p.err = log_error_handler
p.warn = log_warning_handler
}
Expand All @@ -233,7 +237,7 @@ try_build_package :: proc(pkg_name: string) {
ok := parser.parse_file(&p, &file)

if !ok {
if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
if !is_ols_builtin_file(fullpath) {
log.errorf("error in parse file for indexing %v", fullpath)
}
continue
Expand Down Expand Up @@ -293,7 +297,7 @@ index_file :: proc(uri: common.Uri, text: string) -> common.Error {
p := parser.Parser {
flags = {.Optional_Semicolons},
}
if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
if !is_ols_builtin_file(fullpath) {
p.err = log_error_handler
p.warn = log_warning_handler
}
Expand Down Expand Up @@ -328,7 +332,7 @@ index_file :: proc(uri: common.Uri, text: string) -> common.Error {
ok = parser.parse_file(&p, &file)

if !ok {
if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
if !is_ols_builtin_file(fullpath) {
log.errorf("error in parse file for indexing %v", fullpath)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/server/caches.odin
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ clear_all_package_aliases :: proc() {
find_all_package_aliases :: proc() {
for k, v in common.config.collections {
pkgs := make([dynamic]string, context.temp_allocator)
append_packages(v, &pkgs, context.temp_allocator)
append_packages(v, &pkgs, {}, context.temp_allocator)

for pkg in pkgs {
if pkg, err := filepath.rel(v, pkg, context.temp_allocator); err == .None {
Expand Down
120 changes: 95 additions & 25 deletions src/server/check.odin
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import "core:encoding/json"
import "core:fmt"
import "core:log"
import "core:mem"
import "core:os"
import "core:path/filepath"
import path "core:path/slashpath"
import "core:slice"
Expand All @@ -33,20 +32,65 @@ Json_Errors :: struct {
errors: []Json_Error,
}

Check_Mode :: enum {
Saved,
Workspace,
}

//If the user does not specify where to call odin check, it'll just find all directory with odin, and call them seperately.
fallback_find_odin_directories :: proc(config: ^common.Config) -> []string {
data := make([dynamic]string, context.temp_allocator)

if len(config.workspace_folders) > 0 {
if uri, ok := common.parse_uri(config.workspace_folders[0].uri, context.temp_allocator); ok {
append_packages(uri.path, &data, context.temp_allocator)
for workspace in config.workspace_folders {
if uri, ok := common.parse_uri(workspace.uri, context.temp_allocator); ok {
log.error(config.checker_skip_packages)
append_packages(uri.path, &data, config.checker_skip_packages, context.temp_allocator)
}
}

return data[:]
}

path_has_prefix :: proc(path: string, prefix: string) -> bool {
if len(prefix) == 0 || len(path) < len(prefix) {
return false
}

if !strings.equal_fold(path[:len(prefix)], prefix) {
return false
}

if len(path) == len(prefix) {
return true
}

return path[len(prefix)] == '/' || prefix[len(prefix) - 1] == '/'
}

path_matches_checker_scope :: proc(file_path: string, checker_path: string) -> bool {
if filepath.ext(checker_path) == ".odin" {
return strings.equal_fold(file_path, checker_path)
}

return path_has_prefix(file_path, checker_path)
}

clear_check_diagnostics_for_paths :: proc(paths: []string) {
for uri, _ in diagnostics[.Check] {
parsed_uri, ok := common.parse_uri(uri, context.temp_allocator)
if !ok {
continue
}

for checker_path in paths {
if path_matches_checker_scope(parsed_uri.path, checker_path) {
remove_diagnostics(.Check, uri)
break
}
}
}
}

check_unused_imports :: proc(document: ^Document, config: ^common.Config) {
if !config.enable_unused_imports_reporting {
return
Expand Down Expand Up @@ -79,17 +123,35 @@ check_unused_imports :: proc(document: ^Document, config: ^common.Config) {
}
}

check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {
paths := paths
resolve_check_paths :: proc(mode: Check_Mode, uri: common.Uri, config: ^common.Config) -> ([]string, bool) {
if len(config.profile.checker_path) > 0 {
return config.profile.checker_path[:], true
}

if len(paths) == 0 {
if config.enable_checker_only_saved {
paths = {path.dir(uri.path, context.temp_allocator)}
} else {
paths = fallback_find_odin_directories(config)
if mode == .Saved && config.enable_checker_only_saved && uri.path != "" {
paths := make([dynamic]string, context.temp_allocator)
dir := path.dir(uri.path, context.temp_allocator)
if dir not_in config.checker_skip_packages {
append(&paths, dir)
}
return paths[:], false
}

return fallback_find_odin_directories(config), true
}

check :: proc(mode: Check_Mode, uri: common.Uri, config: ^common.Config) {
paths, clear_all := resolve_check_paths(mode, uri, config)

if clear_all {
clear_diagnostics(.Check)
} else {
clear_check_diagnostics_for_paths(paths)
}

if len(paths) == 0 {
return
}

data := make([]byte, mem.Kilobyte * 200, context.temp_allocator)

Expand All @@ -106,11 +168,8 @@ check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {
strings.write_string(&collection_builder, fmt.aprintf("-collection:%v=\"%v\" ", k, v))
}

errors := make(map[string][dynamic]Diagnostic, 0, context.temp_allocator)

clear_diagnostics(.Check)

for path in paths {
builtin_path := config.builtin_path
for check_path in paths {
command: string

if config.odin_command != "" {
Expand All @@ -119,15 +178,15 @@ check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {
command = "odin"
}

entry_point_opt := filepath.ext(path) == ".odin" ? "-file" : "-no-entry-point"
entry_point_opt := filepath.ext(check_path) == ".odin" ? "-file" : "-no-entry-point"

slice.zero(data)

if code, ok, buffer = common.run_executable(
fmt.tprintf(
"%v check \"%s\" %s %s %s %s %s",
command,
path,
check_path,
strings.to_string(collection_builder),
entry_point_opt,
config.checker_args,
Expand All @@ -136,11 +195,10 @@ check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {
),
&data,
); !ok {
log.errorf("Odin check failed with code %v for file %v", code, path)
return
log.errorf("Odin check failed with code %v for file %v", code, check_path)
continue
}


if len(buffer) == 0 {
continue
}
Expand All @@ -150,11 +208,12 @@ check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {
if res := json.unmarshal(buffer, &json_errors, json.DEFAULT_SPECIFICATION, context.temp_allocator);
res != nil {
log.errorf("Failed to unmarshal check results: %v, %v", res, string(buffer))
continue
}

for error in json_errors.errors {
if len(error.msgs) == 0 {
break
continue
}

message := strings.join(error.msgs, "\n", context.temp_allocator)
Expand All @@ -167,20 +226,21 @@ check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {

when ODIN_OS == .Windows {
path = common.get_case_sensitive_path(path, context.temp_allocator)
path, _ = filepath.replace_path_separators(path, '/', context.temp_allocator)
}

folder := filepath.dir(path, context.temp_allocator)
if strings.equal_fold(folder, config.builtin_path) {
if is_ols_builtin_file(path) {
continue
}

uri := common.create_uri(path, context.temp_allocator)

add_diagnostics(
.Check,
uri.uri,
Diagnostic {
code = "checker",
severity = .Error,
severity = map_diagnostic_severity(error.type),
range = {
// odin will sometimes report errors on column 0, so we ensure we don't provide a negative column/line to the client
start = {character = max(error.pos.column - 1, 0), line = max(error.pos.line - 1, 0)},
Expand All @@ -192,3 +252,13 @@ check :: proc(paths: []string, uri: common.Uri, config: ^common.Config) {
}
}
}

@(private = "file")
map_diagnostic_severity :: proc(type: string) -> DiagnosticSeverity {
if strings.equal_fold(type, "warning") {
return .Warning
}

return .Error
}

7 changes: 5 additions & 2 deletions src/server/documents.odin
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,7 @@ document_refresh :: proc(document: ^Document, config: ^common.Config, writer: ^W
return .ParseError
}

if strings.contains(document.uri.uri, "base/builtin/builtin.odin") ||
strings.contains(document.uri.uri, "base/intrinsics/intrinsics.odin") {
if is_ols_builtin_file(document.uri.uri) {
return .None
}

Expand Down Expand Up @@ -497,3 +496,7 @@ get_import_range :: proc(imp: ^ast.Import_Decl, src: string) -> common.Range {
end.character += text_len
return {start = start, end = end}
}

is_ols_builtin_file :: proc(path: string) -> bool {
return strings.has_suffix(path, "/builtin/builtin.odin") || strings.has_suffix(path, "/builtin/intrinsics.odin")
}
4 changes: 2 additions & 2 deletions src/server/references.odin
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ resolve_references :: proc(
p := parser.Parser {
flags = {.Optional_Semicolons},
}
if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
if !is_ols_builtin_file(fullpath) {
p.err = log_error_handler
p.warn = log_warning_handler
}
Expand All @@ -331,7 +331,7 @@ resolve_references :: proc(
ok := parser.parse_file(&p, &file)

if !ok {
if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") {
if !is_ols_builtin_file(fullpath) {
log.errorf("error in parse file for indexing %v", fullpath)
}
continue
Expand Down
Loading