CA UI Implementation


Graphical User Interface

The classes of the graphical user interface of Inspector Eiffel are all located in the interface cluster of EiffelStudio, in the subfolder graphical > tools > code_analysis. Here is a short overview of what the single classes do:

{ES_CODE_ANALYSIS_TOOL} 
Represents the code analysis GUI tool. Contains the tool title and icon, otherwise not much interesting stuff.
{ES_CODE_ANALYSIS_TOOL_PANEL} 
The graphical panel for the code analysis tool. It contains buttons, labels, the rule violations table view, and other user interface elements.
{ES_CODE_ANALYSIS_COMMAND} 
The command to launch the code analyzer. It can be added to toolbars and menus. It can be executed using stones. This class also handles the caching.
{ES_CODE_ANALYSIS_BENCH_HELPER} 
A helper class for the integration of the code analysis tool. It contains shared instances of {CA_CODE_ANALYZER} and {ES_CODE_ANALYSIS_COMMAND}, which are used by the GUI.
{ES_CA_SHOW_PREFERENCES_COMMAND} 
The command is used by the Preferences button in the panel.
{ES_CA_FIX_EXECUTOR} 
This class fixes a rule violation that has been found by the code analysis tool.

These are roughly the class relations for the Inspector Eiffel GUI:

The most interesting class relations of the Inspector Eiffel GUI.

Caching

It is a common case that GUI users run the code analyzer again after having made some changes to the code. We do not need to analyze the same unchanged code again and again. Therefore code analysis caches the results in memory. This only applies to the GUI mode.

Code analysis uses cached results exactly in one case: when the whole system is analyzed and the previous analysis was on the whole system, too.

The caching functionality is implemented in {ES_CODE_ANALYSIS_COMMAND}. When the command for analyzing the system is executed, the timestamps of the last modification of the classes are stored in analysis_timestamp : HASH_TABLE [INTEGER, CLASS_I] before the analysis. Note that the cached results (the rule violations) themselves are managed by {CA_CODE_ANALYZER}. The only difference to a non-cached analysis is that the rule violations are not deleted by {ES_CODE_ANALYSIS_COMMAND} before the next analysis. Then, in case the next command is also for analyzing the whole system, the current timestamps are compared to the stored timestamps. Any class that has been changed in the meantime will be analyzed again; for any unchanged class the rule violations are taken from the cache.

Example Command: Analyze One Class

We will now roughly go through the code that is executed on the GUI part when the user wants to analyze a single class. As mentioned in Chapter Running the Analyzer, this can be done using the class context menu or by dropping the class stone on the button Analyze Item.

In any case {ES_CODE_ANALYSIS_COMMAND}.execute_with_stone is called, which delegates to execute_with_stone_content:

execute_with_stone (a_stone: STONE)
    -- Execute with `a_stone'.
  do
    execute_with_stone_content (a_stone, Void)
  end
 
execute_with_stone_content (a_stone: STONE; a_content: SD_CONTENT)
    -- Execute with `a_stone'.
  local
    l_save_confirm: ES_DISCARDABLE_COMPILE_SAVE_FILES_PROMPT
    l_classes: DS_ARRAYED_LIST [CLASS_I]
  do
      -- Show the tool right from the start.
    show_ca_tool
 
    if not eiffel_project.is_compiling then
      if window_manager.has_modified_windows then
        create l_classes.make_default
        window_manager.all_modified_classes.do_all (agent l_classes.force_last)
        create l_save_confirm.make (l_classes)
        l_save_confirm.set_button_action (l_save_confirm.dialog_buttons.yes_button, agent save_compile_and_analyze (a_stone))
        l_save_confirm.set_button_action (l_save_confirm.dialog_buttons.no_button, agent compile_and_analyze (a_stone))
        l_save_confirm.show_on_active_window
      else
        compile_and_analyze (a_stone)
      end
    end
  end

If there are modified, unsaved windows a save confirmation dialog is displayed. Eventually program flow passes on to compile_and_analyze:

compile_and_analyze (a_stone: STONE)
    -- Compile project and perform analysis of stone `a_stone'.
  local
    l_helper: ES_CODE_ANALYSIS_BENCH_HELPER
    l_dialog: ES_INFORMATION_PROMPT
  do
      -- Compile the project and only analyze if the compilation was successful.
    eiffel_project.quick_melt (True, True, True)
    if eiffel_project.successful then
      create l_helper
      if l_helper.code_analyzer.is_running then
        create l_dialog.make_standard (ca_messages.already_running_long)
        l_dialog.show_on_active_window
      else
        perform_analysis (a_stone)
      end
    end
  end

eiffel_project.quick_melt starts the compilation. A successful compilation is required for code analysis; otherwise nothing is analyzed. After compilation has succeeded we check if code analysis is already running. If this is the case then a dialog is displayed. If on the other hand this last possible obstacle is not present we finally start analyzing by calling perform_analysis.

perform_analysis (a_stone: STONE)
    -- Analyze `a_stone' only.
  local
    l_helper: ES_CODE_ANALYSIS_BENCH_HELPER
    l_scope_label: EV_LABEL
  do
      -- For simplicity let us assume that `a_stone' does not
      -- correspond to the system or is equivalent to it.
    last_was_analyze_all := False
 
    create l_helper
    code_analyzer := l_helper.code_analyzer
    code_analyzer.clear_classes_to_analyze
    code_analyzer.rule_violations.wipe_out
 
    l_scope_label := ca_tool.panel.scope_label
 
    if attached {CLASSC_STONE} a_stone as s then
      code_analyzer.add_class (s.class_i.config_class)
      l_scope_label.set_text (s.class_name)
      l_scope_label.set_foreground_color (create {EV_COLOR}.make_with_8_bit_rgb (140, 140, 255))
      l_scope_label.set_pebble (s)
      l_scope_label.set_pick_and_drop_mode
      l_scope_label.set_tooltip (ca_messages.class_scope_tooltip)
    elseif [...]
    [...]
    end
 
    disable_tool_button
    window_manager.display_message (ca_messages.status_bar_running)
    code_analyzer.add_completed_action (agent analysis_completed)
    code_analyzer.analyze
  end

(The code that deals with stones other than classes is omitted.)

At the start of the routine the code analyzer instance is retrieved from the helper class. All classes that may have been addedbefore, are removed. All previous rule violations are removed as well. The if clause creates a stone for the Last Scope label in the graphical panel. Then, the button in the tool is disabled so that starting another analysis is prevented until the current one has completed. Finally, the analysis is started. As soon as the analysis has completed {ES_CODE_ANALYSIS_COMMAND}.analysis_completed is called. In this procedure the rule violations (and possibly the exceptions) are retrieved from the code analyzer and displayed in the list in the tool panel.

Command-Line Interface

The whole command-line functionality of the code analyzer is located in the class {EWB_CODE_ANALYSIS}. It is located in the tty cluster of EiffelStudio. {EWB_CODE_ANALYSIS} is invoked by {ES}, the root class for the batch (command-line) version of EiffelStudio. In {ES}, the invocation looks as follows:

elseif option.is_equal ("-code-analysis") then
  l_at_args := arguments_in_range (current_option + 1, argument_count)
  current_option := argument_count + 1
  create {EWB_CODE_ANALYSIS} command.make_with_arguments (l_at_args)

Any command-line arguments after -code-analysis are passed on to {EWB_CODE_ANALYSIS}. This class, in its creation procedure, processes the arguments as described in Command Line Usage. Classes that were passed as command-line arguments are added to the analyzer. Then the actual execution happens in the procedure execute. EWB_CODE_ANALYSIS of course uses the code_analysis library and the previously described interface of CA_CODE_ANALYZER. After analysis a list of rule violations is output to the command-line. In the code it looks like this:

across l_code_analyzer.rule_violations as l_vlist loop
  if not l_vlist.item.is_empty then
    l_has_violations := True
      -- Always sort the rule violations by the class they are referring to.
    output_window.add (ca_messages.cmd_class + l_vlist.key.name + "':%N")
 
      -- See `{CA_RULE_VIOLATION}.is_less' for information on the sorting.
    across l_vlist.item as ic loop
      l_rule_name := ic.item.rule.title
      l_rule_id := ic.item.rule.id
      if attached ic.item.location as l_loc then
        l_line := ic.item.location.line.out
        l_col := ic.item.location.column.out
        output_window.add ("  (" + l_line + ":" + l_col + "): "
          + l_rule_name + " (" + l_rule_id + "): ")
      else -- No location attached. Print without location.
        output_window.add ("  "	+ l_rule_name + " (" + l_rule_id + "): ")
      end
      ic.item.format_violation_description (output_window)
      output_window.add ("%N")
    end
  end
end
 
if not l_has_violations then output_window.add (ca_messages.no_issues + "%N") end