Module: Expressir::Coverage

Defined in:
lib/expressir/coverage.rb

Overview

Coverage module for calculating documentation coverage of EXPRESS entities

Defined Under Namespace

Classes: Report

Constant Summary collapse

ENTITY_TYPE_MAP =

Mapping of EXPRESS entity type names to their corresponding class names

{
  "TYPE" => "Expressir::Model::Declarations::Type",
  "ENTITY" => "Expressir::Model::Declarations::Entity",
  "CONSTANT" => "Expressir::Model::Declarations::Constant",
  "FUNCTION" => "Expressir::Model::Declarations::Function",
  "RULE" => "Expressir::Model::Declarations::Rule",
  "PROCEDURE" => "Expressir::Model::Declarations::Procedure",
  "SUBTYPE_CONSTRAINT" => "Expressir::Model::Declarations::SubtypeConstraint",
  "PARAMETER" => "Expressir::Model::Declarations::Parameter",
  "VARIABLE" => "Expressir::Model::Declarations::Variable",
  "ATTRIBUTE" => "Expressir::Model::Declarations::Attribute",
  "DERIVED_ATTRIBUTE" => "Expressir::Model::Declarations::DerivedAttribute",
  "INVERSE_ATTRIBUTE" => "Expressir::Model::Declarations::InverseAttribute",
  "UNIQUE_RULE" => "Expressir::Model::Declarations::UniqueRule",
  "WHERE_RULE" => "Expressir::Model::Declarations::WhereRule",
  "ENUMERATION_ITEM" => "Expressir::Model::DataTypes::EnumerationItem",
  "INTERFACE" => "Expressir::Model::Declarations::Interface",
  "INTERFACE_ITEM" => "Expressir::Model::Declarations::InterfaceItem",
  "INTERFACED_ITEM" => "Expressir::Model::Declarations::InterfacedItem",
  "SCHEMA_VERSION" => "Expressir::Model::Declarations::SchemaVersion",
  "SCHEMA_VERSION_ITEM" => "Expressir::Model::Declarations::SchemaVersionItem",
}.freeze
CLASS_TO_EXPRESS_TYPE_MAP =

Mapping of class names to EXPRESS entity type names (for proper formatting)

{
  "Type" => "TYPE",
  "Entity" => "ENTITY",
  "Constant" => "CONSTANT",
  "Function" => "FUNCTION",
  "Rule" => "RULE",
  "Procedure" => "PROCEDURE",
  "SubtypeConstraint" => "SUBTYPE_CONSTRAINT",
  "Parameter" => "PARAMETER",
  "Variable" => "VARIABLE",
  "Attribute" => "ATTRIBUTE",
  "DerivedAttribute" => "DERIVED_ATTRIBUTE",
  "InverseAttribute" => "INVERSE_ATTRIBUTE",
  "UniqueRule" => "UNIQUE_RULE",
  "WhereRule" => "WHERE_RULE",
  "EnumerationItem" => "ENUMERATION_ITEM",
  "Interface" => "INTERFACE",
  "InterfaceItem" => "INTERFACE_ITEM",
  "InterfacedItem" => "INTERFACED_ITEM",
  "SchemaVersion" => "SCHEMA_VERSION",
  "SchemaVersionItem" => "SCHEMA_VERSION_ITEM",
}.freeze
TYPE_SUBTYPES =

Available TYPE subtypes based on data types

%w[
  AGGREGATE ARRAY BAG BINARY BOOLEAN ENUMERATION GENERIC GENERIC_ENTITY
  INTEGER LIST LOGICAL NUMBER REAL SELECT SET STRING
].freeze

Class Method Summary collapse

Class Method Details

.apply_exclusions(entities, exclusions) ⇒ Array<Expressir::Model::ModelElement>

Apply exclusions to filter out entities based on skip_types (supports both simple and TYPE:SUBTYPE syntax)

Parameters:

Returns:



479
480
481
# File 'lib/expressir/coverage.rb', line 479

def self.apply_exclusions(entities, exclusions)
  filter_skipped_entities(entities, exclusions)
end

.entity_documented?(entity) ⇒ Boolean

Check if an entity has documentation (remarks)

Parameters:

Returns:

  • (Boolean)

    True if the entity has documentation



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/expressir/coverage.rb', line 283

def self.entity_documented?(entity)
  # Check for direct remarks
  if entity.respond_to?(:remarks) && entity.remarks && !entity.remarks.empty?
    return true
  end

  # Check for remark_items
  if entity.respond_to?(:remark_items) && entity.remark_items && !entity.remark_items.empty?
    return true
  end

  # For schema entities, check if there's a remark_item with their ID
  if entity.parent.respond_to?(:remark_items) && entity.parent.remark_items
    entity_id = entity.id.to_s.downcase
    entity.parent.remark_items.any? do |item|
      item.id.to_s.downcase == entity_id || item.id.to_s.downcase.include?("#{entity_id}.")
    end
  else
    false
  end
end

.filter_skipped_entities(entities, skip_types) ⇒ Array<Expressir::Model::ModelElement>

Filter out entities based on skip_types (supports both simple and TYPE:SUBTYPE syntax)

Parameters:

Returns:



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/expressir/coverage.rb', line 487

def self.filter_skipped_entities(entities, skip_types)
  return entities if skip_types.empty?

  # Parse skip_types into simple types, TYPE subtypes, and FUNCTION subtypes
  simple_skips = []
  type_subtype_skips = []
  function_subtype_skips = []

  skip_types.each do |skip_type|
    if skip_type.include?(":")
      # Handle TYPE:SUBTYPE and FUNCTION:SUBTYPE format
      main_type, subtype = skip_type.split(":", 2)
      if main_type == "TYPE" && TYPE_SUBTYPES.include?(subtype)
        type_subtype_skips << subtype
      elsif main_type == "FUNCTION" && subtype == "INNER"
        function_subtype_skips << subtype
      end
    else
      # Handle simple type format
      simple_skips << skip_type
    end
  end

  # Filter entities
  entities.reject do |entity|
    entity_class = entity.class.name

    # Check simple type exclusions
    # Convert entity class to EXPRESS type name for comparison
    class_name = entity_class.split("::").last
    express_type = CLASS_TO_EXPRESS_TYPE_MAP[class_name] || class_name.upcase

    if simple_skips.include?(express_type)
      true
    # Check TYPE subtype exclusions
    elsif entity_class == "Expressir::Model::Declarations::Type" && type_subtype_skips.any?
      entity_subtype = get_type_subtype(entity)
      type_subtype_skips.include?(entity_subtype)
    # Check FUNCTION:INNER exclusions
    elsif entity_class == "Expressir::Model::Declarations::Function" && function_subtype_skips.include?("INNER")
      inner_function?(entity)
    else
      false
    end
  end
end

.find_entities(schema_or_repo, skip_types = []) ⇒ Array<Expressir::Model::ModelElement>

Find all entities in a schema or repository

Parameters:

Returns:



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/expressir/coverage.rb', line 309

def self.find_entities(schema_or_repo, skip_types = [])
  entities = []

  # Handle both repository and schema inputs
  if schema_or_repo.is_a?(Expressir::Model::Repository)
    # If it's a repository, process all schemas
    schema_or_repo.schemas.each do |schema|
      entities.concat(find_entities_in_schema(schema))
    end
  else
    # If it's a schema, process it directly
    entities.concat(find_entities_in_schema(schema_or_repo))
  end

  # Filter out any nil elements and ensure all have IDs
  entities = entities.compact.select { |e| e.respond_to?(:id) && e.id }

  # Filter out skipped entity types
  apply_exclusions(entities, skip_types)
end

.find_entities_in_schema(schema) ⇒ Array<Expressir::Model::ModelElement>

Find all entities in a single schema

Parameters:

Returns:



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/expressir/coverage.rb', line 333

def self.find_entities_in_schema(schema)
  entities = []

  # Add all schema-level entities
  entities.concat(schema.constants) if schema.constants
  entities.concat(schema.types) if schema.types
  entities.concat(schema.entities) if schema.entities
  entities.concat(schema.functions) if schema.functions
  entities.concat(schema.rules) if schema.rules
  entities.concat(schema.procedures) if schema.procedures
  entities.concat(schema.subtype_constraints) if schema.subtype_constraints
  entities.concat(schema.interfaces) if schema.interfaces

  # Add nested entities recursively
  entities.concat(find_nested_entities(schema))

  entities
end

.find_nested_entities(container) ⇒ Array<Expressir::Model::ModelElement>

Find all nested entities within a container (schema, entity, function, etc.)

Parameters:

Returns:



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/expressir/coverage.rb', line 355

def self.find_nested_entities(container)
  entities = []

  # Handle different container types
  case container
  when Expressir::Model::Declarations::Schema
    # Schema-level nested entities
    container.types&.each do |type|
      entities.concat(find_nested_entities(type))
    end
    container.entities&.each do |entity|
      entities.concat(find_nested_entities(entity))
    end
    container.functions&.each do |function|
      entities.concat(find_nested_entities(function))
    end
    container.rules&.each do |rule|
      entities.concat(find_nested_entities(rule))
    end
    container.procedures&.each do |procedure|
      entities.concat(find_nested_entities(procedure))
    end
    container.interfaces&.each do |interface|
      entities.concat(find_nested_entities(interface))
    end

  when Expressir::Model::Declarations::Type
    # Type nested entities
    if container.respond_to?(:enumeration_items) && container.enumeration_items
      entities.concat(container.enumeration_items)
    end

  when Expressir::Model::Declarations::Entity
    # Entity nested entities
    entities.concat(container.attributes) if container.attributes
    entities.concat(container.unique_rules) if container.unique_rules
    entities.concat(container.where_rules) if container.where_rules

  when Expressir::Model::Declarations::Function
    # Function nested entities
    entities.concat(container.parameters) if container.parameters
    entities.concat(container.variables) if container.variables
    entities.concat(container.constants) if container.constants
    entities.concat(container.types) if container.types
    entities.concat(container.entities) if container.entities
    entities.concat(container.functions) if container.functions
    entities.concat(container.procedures) if container.procedures
    entities.concat(container.subtype_constraints) if container.subtype_constraints

    # Recursively find nested entities in nested containers
    container.types&.each do |type|
      entities.concat(find_nested_entities(type))
    end
    container.entities&.each do |entity|
      entities.concat(find_nested_entities(entity))
    end
    container.functions&.each do |function|
      entities.concat(find_nested_entities(function))
    end
    container.procedures&.each do |procedure|
      entities.concat(find_nested_entities(procedure))
    end

  when Expressir::Model::Declarations::Rule
    # Rule nested entities
    entities.concat(container.variables) if container.variables
    entities.concat(container.constants) if container.constants
    entities.concat(container.types) if container.types
    entities.concat(container.entities) if container.entities
    entities.concat(container.functions) if container.functions
    entities.concat(container.procedures) if container.procedures
    entities.concat(container.subtype_constraints) if container.subtype_constraints

    # Recursively find nested entities in nested containers
    container.types&.each do |type|
      entities.concat(find_nested_entities(type))
    end
    container.entities&.each do |entity|
      entities.concat(find_nested_entities(entity))
    end
    container.functions&.each do |function|
      entities.concat(find_nested_entities(function))
    end
    container.procedures&.each do |procedure|
      entities.concat(find_nested_entities(procedure))
    end

  when Expressir::Model::Declarations::Procedure
    # Procedure nested entities
    entities.concat(container.parameters) if container.parameters
    entities.concat(container.variables) if container.variables
    entities.concat(container.constants) if container.constants
    entities.concat(container.types) if container.types
    entities.concat(container.entities) if container.entities
    entities.concat(container.functions) if container.functions
    entities.concat(container.procedures) if container.procedures
    entities.concat(container.subtype_constraints) if container.subtype_constraints

    # Recursively find nested entities in nested containers
    container.types&.each do |type|
      entities.concat(find_nested_entities(type))
    end
    container.entities&.each do |entity|
      entities.concat(find_nested_entities(entity))
    end
    container.functions&.each do |function|
      entities.concat(find_nested_entities(function))
    end
    container.procedures&.each do |procedure|
      entities.concat(find_nested_entities(procedure))
    end

  when Expressir::Model::Declarations::Interface
    # Interface nested entities
    entities.concat(container.items) if container.items
  end

  entities
end

.get_type_subtype(type_entity) ⇒ String

Get the subtype of a TYPE entity based on its underlying_type

Parameters:

Returns:

  • (String)

    The subtype name (e.g., “SELECT”, “ENUMERATION”)



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/expressir/coverage.rb', line 537

def self.get_type_subtype(type_entity)
  return nil unless type_entity.respond_to?(:underlying_type) && type_entity.underlying_type

  # Get the class name of the underlying type
  underlying_class = type_entity.underlying_type.class.name

  # Extract the data type name (e.g., "Select" from "Expressir::Model::DataTypes::Select")
  if underlying_class.start_with?("Expressir::Model::DataTypes::")
    data_type_name = underlying_class.split("::").last
    # Convert to uppercase (e.g., "Select" -> "SELECT")
    data_type_name.upcase
  else
    # For other types, try to extract the last part of the class name
    underlying_class.split("::").last&.upcase
  end
end

.inner_function?(function_entity) ⇒ Boolean

Check if a function is an inner function (nested within another function, rule, or procedure)

Parameters:

Returns:

  • (Boolean)

    True if the function is nested within another function, rule, or procedure



557
558
559
560
561
562
563
564
565
# File 'lib/expressir/coverage.rb', line 557

def self.inner_function?(function_entity)
  return false unless function_entity.respond_to?(:parent) && function_entity.parent

  # Check if the parent is a function, rule, or procedure (not a schema)
  parent = function_entity.parent
  parent.is_a?(Expressir::Model::Declarations::Function) ||
    parent.is_a?(Expressir::Model::Declarations::Rule) ||
    parent.is_a?(Expressir::Model::Declarations::Procedure)
end