review

review patchsets using your default editor
Log | Files | Refs

odin.odin (4209B)


      1 package analyser
      2 
      3 import "core:encoding/json"
      4 import "core:os"
      5 import "core:strings"
      6 
      7 import "../txt"
      8 
      9 // odin_check type-checks each package the change touched with every vet
     10 // switch and the compiler's own style, and reads its JSON errors. The
     11 // compiler writes its JSON to stderr and exits non-zero when it has
     12 // errors, which is the answer, not a failure.
     13 odin_check := Analyser {
     14 	name = "odin-check",
     15 	covers = proc(path: string) -> bool {return strings.has_suffix(path, ".odin")},
     16 	ready = proc(tree_dir: string) -> (bool, string) {return on_path("odin"), ""},
     17 	run = proc(tree_dir, root: string, files: []string) -> ([]Diagnostic, string) {
     18 		dirs := make(map[string]bool, context.temp_allocator)
     19 		for f in files {
     20 			dirs[dir_of(f)] = true
     21 		}
     22 		found := make([dynamic]Diagnostic)
     23 		for dir in sorted(dirs) {
     24 			args := make([dynamic]string, context.temp_allocator)
     25 			append(&args, "check", dir, "-vet", "-strict-style", "-json-errors", "-no-entry-point")
     26 			append(&args, ..collections(tree_dir, root))
     27 			stdout, stderr, err := execute_both(tree_dir, "odin", args[:], context.temp_allocator)
     28 			out := strings.concatenate({stdout, stderr}, context.temp_allocator)
     29 			if err != "" && !strings.contains(out, "error_count") {
     30 				return found[:], strings.concatenate({"odin: ", tail(out, 200)})
     31 			}
     32 			append(&found, ..parse_odin(tree_dir, out, context.temp_allocator))
     33 		}
     34 		return found[:], ""
     35 	},
     36 }
     37 
     38 // collections are the -collection flags the ols.json at the end of the
     39 // change declares, which is where an Odin project names the collections
     40 // its imports resolve through; a relative path is relative to the
     41 // repository the change is in.
     42 collections :: proc(tree_dir, root: string, allocator := context.temp_allocator) -> []string {
     43 	Config :: struct {
     44 		collections: []struct {
     45 			name: string `json:"name"`,
     46 			path: string `json:"path"`,
     47 		} `json:"collections"`,
     48 	}
     49 	data, err := os.read_entire_file_from_path(join(tree_dir, "ols.json"), context.temp_allocator)
     50 	if err != nil {
     51 		return nil
     52 	}
     53 	config: Config
     54 	if json.unmarshal(data, &config, allocator = context.temp_allocator) != nil {
     55 		return nil
     56 	}
     57 	flags := make([dynamic]string, allocator)
     58 	for c in config.collections {
     59 		if c.name == "" || c.path == "" {
     60 			continue
     61 		}
     62 		// A relative path is relative to the repository, not to the
     63 		// scratch copy a range is read from, which has no neighbours.
     64 		path := c.path
     65 		if !strings.has_prefix(path, "/") {
     66 			path = join(root, path, allocator)
     67 		}
     68 		append(&flags, strings.concatenate({"-collection:", c.name, "=", path}, allocator))
     69 	}
     70 	return flags[:]
     71 }
     72 
     73 // Odin_Report is the compiler's -json-errors.
     74 Odin_Report :: struct {
     75 	errors: []struct {
     76 		type: string `json:"type"`,
     77 		pos:  struct {
     78 			file: string `json:"file"`,
     79 			line: int `json:"line"`,
     80 		} `json:"pos"`,
     81 		msgs: []string `json:"msgs"`,
     82 	} `json:"errors"`,
     83 }
     84 
     85 // parse_odin reads the compiler's -json-errors: a type error is a fault,
     86 // a vet failure the vet's opinion, a style failure a note. The compiler
     87 // prints its own prose before the JSON when it cannot even start; the
     88 // object is taken from wherever it sits.
     89 parse_odin :: proc(tree_dir, out: string, allocator := context.allocator) -> []Diagnostic {
     90 	found := make([dynamic]Diagnostic, allocator)
     91 	raw, ok := txt.object(out)
     92 	if !ok {
     93 		return found[:]
     94 	}
     95 	report: Odin_Report
     96 	if json.unmarshal_string(raw, &report, allocator = context.temp_allocator) != nil {
     97 		return found[:]
     98 	}
     99 	for e in report.errors {
    100 		message := strings.join(e.msgs, "; ", allocator)
    101 		d := Diagnostic {
    102 			file     = strings.clone(relative(tree_dir, e.pos.file), allocator),
    103 			line     = e.pos.line,
    104 			message  = message,
    105 			severity = .Must_Fix,
    106 			fault    = true,
    107 		}
    108 		switch {
    109 		case strings.contains(message, "-strict-style"):
    110 			d.code, d.severity, d.fault = "style", .Note, false
    111 		case e.type == "warning":
    112 			d.severity, d.fault = .Consider, false
    113 		case strings.contains(message, "declared but not used"),
    114 		     strings.contains(message, "shadow"):
    115 			// A vet failure fails the build under -vet, but it is the
    116 			// vet's opinion, not a type error.
    117 			d.code, d.severity, d.fault = "vet", .Consider, false
    118 		}
    119 		append(&found, d)
    120 	}
    121 	return found[:]
    122 }