# encoding: utf-8 # frozen_string_literal: true module Carbon module Compiler class Metanostic # A list of diagnostics. This handles setting up the modes of # new, emitted diagnostics, and reporting diagnostic types. class List include Enumerable extend Forwardable # The current state. This contains information about what modes each # diagnostic should inherit. # # @return [State] attr_reader :state # The default hash of metanostics. # # @see Defaults.defaults # @return [{::Hash => Metanostic}] attr_reader :defaults # The project files for the metanostic list. This is to provide context # for diagnostic errors. # # @retrun [{::String => Project::File}] attr_reader :files THRESHOLDS = { "warnings" => 100, "errors" => 20 }.freeze delegate [:each, :size, :count, :length, :clear] => :@list # Initialize the list. def initialize @defaults = Metanostic::Defaults.defaults @state = State.new @list = Concurrent::Array.new @files = Concurrent::Hash.new end # Returns a list of diagnostics that have the {Mode::PANIC} mode. # # @return [] def panics select { |d| d.metanostic.default == Mode::PANIC } end # Returns a list of diagnostics that have the {Mode::ERROR} mode. # # @return [] def errors select { |d| d.metanostic.default == Mode::ERROR } end # Returns a list of diagnostics that have the {Mode::WARNING} mode. # # @return [] def warnings select { |d| d.metanostic.default == Mode::WARNING } end # Emits a diagnostic to the list. It sets up the data to pass to the # diagnostic. If the diagnostic cannot be found, it instead emits a # `System/Error` diagnostic. def emit(name, location = Location.default, format = []) mode, metanostic = @state.fetch(name) do return emit("System/Error", location, [name]) end message = format.is_a?(::String) ? format : sprintf(metanostic.message, *format) stack = caller[2..7] diagnostic = Diagnostic.new(meta: metanostic, location: location, message: message, mode: mode, list: self, stack: stack) @list << diagnostic diagnostic.output($stderr) diagnostic_check end alias_method :<<, :emit alias_method :push, :emit # Outputs all of the diagnostics in this list. Does nothing if # {Carbon.quiet?} is true. # # @see Diagnostic#output # @param io [#<<] The IO device to output to. # @return [void] def output(io) return if Carbon.quiet? each { |i| i.output(io, @files) } end private def diagnostic_check fail DiagnosticError, "Exceeded warning threshold" if \ warnings.size >= THRESHOLDS.fetch("warnings") fail DiagnosticError, "Exceeded error threshold" if \ errors.size >= THRESHOLDS.fetch("errors") fail DiagnosticError, "Exceeded panic threshold" if panics.size >= 1 end end end end end