#!/usr/bin/env rexx
/******************************************************************************/
/*                                                                            */
/* Warnings.testGroup - Warnings and error hints (GitHub issue #39)           */
/* ================================================================           */
/*                                                                            */
/* This program is part of the Rexx Parser package                            */
/* [See https://rexx.epbcn.com/rexx-parser/]                                  */
/*                                                                            */
/* Copyright (c) 2026 Josep Maria Blasco <josep.maria.blasco@epbcn.com>       */
/*                                                                            */
/* License: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0)  */
/*                                                                            */
/* Version history:                                                           */
/*                                                                            */
/* Date     Version Details                                                   */
/* -------- ------- --------------------------------------------------------- */
/* 20260930    0.7  First version                                             */
/*                                                                            */
/******************************************************************************/

parse source . . fileSpec
group = .TestGroup~new(fileSpec)
group~add(.Warnings.testGroup)
If group~isAutomatedTest Then
  Return group
Else
  Return group~suite~execute~~print

::Requires "ooTest.frm"
::Requires "ParserTestCase.cls"
::Requires "parser/Warnings.cls"

::Class Warnings.testGroup Subclass ParserTestCase Public

/******************************************************************************/
/* Helpers                                                                    */
/******************************************************************************/

-- The error directory of the parser for a source ("" when it parses)
::Method errorOf
  Use Strict Arg source
  options = .Array~of( (earlyCheck, (BIFs, SIGNAL, GUARD, EXPOSE, USELOCAL, -
    LEAVE, ITERATE, PARSE)) )
  Signal On Syntax
  .Rexx.Parser~new("", source, options)
  Return ""
Syntax:
  co = Condition("O")
  If co~code \== 98.900 Then Return "Unexpected" co~code
  Return co~additional~lastItem

-- Error code and hint ("" for none)
::Method checkHint
  Use Strict Arg source, code, hint
  error = self~errorOf( source )
  If source~isA(.Array) Then source = source~makeString("L", "|")
  If \error~isA(.Directory) Then Do
    self~assertTrue(.False, source "-> no error, expected" code)
    Return
  End
  got = ""
  If error~hasIndex("HINT") Then got = error~hint
  self~assertTrue(error~code == code, source "-> ["error~code"], expected" code)
  If hint == "" Then
    self~assertTrue(got == "", source "-> hint ["got"], expected none")
  Else
    self~assertTrue(got~pos(hint) > 0, -
      source "-> hint ["got"], expected ["hint"...]")

-- The warnings: "ID line col" for each, blank-separated, in order
::Method warningsOf
  Use Strict Arg source
  package = .Rexx.Parser~new("", source)~package
  list = ""
  Do w Over Warnings( package )
    list = list w~id w~position w~column
  End
  Return Strip(list)

::Method checkWarnings
  Use Strict Arg source, expected
  got = self~warningsOf( source )
  If source~isA(.Array) Then source = source~makeString("L", "|")
  self~assertTrue(got == expected, source "-> ["got"], expected ["expected"]")

/******************************************************************************/
/* Hints: the error is the same as ooRexx's, and it has a hint                */
/******************************************************************************/

::Method test_expose_hint_same_error
  -- The hints do not change the error
  self~parserError(99.907, ("::Class C", "::Method M", "a: Expose x"))
  self~parserError(99.907, ("Call S", "Exit", "S: Procedure", "Expose x"))
  self~parserError(99.907, ("x = 1", "Expose y"))
  self~parserError(99.907, ("a: Expose y"))

::Method test_expose_hint
  -- After a label, in a method: the label is a clause
  self~checkHint(("::Class C", "::Method M", "a: Expose x"), 99.907, -
    "A label is a clause")
  self~checkHint(("::Class C", "::Method M", "a:", "Expose x"), 99.907, -
    "A label is a clause")
  -- After a label, out of a method: PROCEDURE EXPOSE, with the label
  self~checkHint("a: Expose y", 99.907, '"a: PROCEDURE EXPOSE ..."')
  -- After PROCEDURE, in another clause
  self~checkHint(("Call S", "Exit", "S: Procedure", "Expose x"), 99.907, -
    "write PROCEDURE EXPOSE in the same clause")
  self~checkHint(("Call S", "Exit", "S: Procedure; Expose x"), 99.907, -
    "write PROCEDURE EXPOSE in the same clause")
  -- Anything else, out of a method
  self~checkHint(("x = 1", "Expose y"), 99.907, -
    "in an internal routine, variables are exposed with PROCEDURE EXPOSE")
  self~checkHint(("Call S", "Exit", "S: Procedure Expose a; Expose x"), -
    99.907, "in an internal routine")
  -- In a method, after other instructions: no hint
  self~checkHint(("::Class C", "::Method M", "x = 1", "Expose x"), 99.907, "")

::Method test_expose_hint_98_992
  -- EXPOSE first in a ::ROUTINE or in the prolog (early check EXPOSE)
  self~checkHint(("Call R", "::Routine R", "Expose x"), 98.992, -
    "A ::ROUTINE has no object variables")
  self~checkHint("Expose x", 98.992, "The prolog of a program is not a method")

::Method test_no_hint_elsewhere
  self~checkHint("Say (", 35.1, "")

/******************************************************************************/
/* INHERIT.OPTION                                                             */
/******************************************************************************/

::Method test_inherit_option
  self~checkWarnings(("::Class C Inherit D Public", "::Class D Mixinclass Object"), -
    "INHERIT.OPTION 1 21")
  self~checkWarnings(("::Class C Inherit D private ABSTRACT",                 -
                      "::Class D Mixinclass Object"),                          -
    "INHERIT.OPTION 1 21 INHERIT.OPTION 1 29")
  self~checkWarnings(("::Class C Inherit D Inherit Metaclass Subclass",       -
                      "::Class D Mixinclass Object"),                          -
    "INHERIT.OPTION 1 21 INHERIT.OPTION 1 29 INHERIT.OPTION 1 39")
  -- Options before INHERIT are fine
  self~checkWarnings(("::Class C Public Inherit D",                           -
                      "::Class D Mixinclass Object"), "")
  -- A string, a namespace, or a class of the package: no warning
  self~checkWarnings(("::Class C Inherit D ""PUBLIC""",                       -
                      "::Class D Mixinclass Object"), "")
  self~checkWarnings(("::Class C Inherit D x:Public",                         -
                      "::Class D Mixinclass Object"), "")
  self~checkWarnings(("::Class C Inherit D Abstract",                         -
                      "::Class D Mixinclass Object",                           -
                      "::Class Abstract Mixinclass Object"), "")

/******************************************************************************/
/* LABEL.NAMESPACE                                                            */
/******************************************************************************/

::Method test_label_namespace
  self~checkWarnings('rexx:string~new("a")',   "LABEL.NAMESPACE 1 1")
  self~checkWarnings("ns:f(1)",                "LABEL.NAMESPACE 1 1")
  self~checkWarnings("ns:cmd",                 "LABEL.NAMESPACE 1 1")
  self~checkWarnings(("Nop", "  a:b~c"),        "LABEL.NAMESPACE 2 3")
  -- Blanks and comments change nothing: still a label
  self~checkWarnings("rexx: string~new('a')",  "LABEL.NAMESPACE 1 1")
  self~checkWarnings("rexx  :  string~new",    "LABEL.NAMESPACE 1 1")
  self~checkWarnings("a:/* c */b~c",           "LABEL.NAMESPACE 1 1")
  -- The instruction on another line: a label is what was meant
  self~checkWarnings(("a:", "b~c"),            "")
  self~checkWarnings(("a: /* c", "*/ b~c"),    "")
  -- The qualified name in parentheses: no label at all
  self~checkWarnings("(rexx:string)~new('a')", "")
  -- Keyword instructions, assignments, labels, environment symbols,
  -- numbers, strings and comments after the ":"
  self~checkWarnings("x:say 1",                "")
  self~checkWarnings("a:b = 1",                "")
  self~checkWarnings("a:b:",                   "")
  self~checkWarnings("a:.x~y",                 "")
  self~checkWarnings("a:1",                    "")
  self~checkWarnings("a:'cmd'",                "")
  -- A label that is a string is never a namespace
  self~checkWarnings('"s":z~w',                "")
  -- Nor one that starts with a period
  self~checkWarnings(".x:y~z",                 "")
  -- The label of a SIGNAL is not a label definition
  self~checkWarnings(("Signal a", "a: Nop"),   "")

::Method test_warnings_sorted
  self~checkWarnings(("x:y~z", "Exit", "::Class C Inherit D Public", -
                      "::Class D Mixinclass Object"),                  -
    "LABEL.NAMESPACE 1 1 INHERIT.OPTION 3 21")

::Method test_warning_clause
  package = .Rexx.Parser~new("", ("Nop; x:y~z; Nop"))~package
  w = Warnings( package )
  self~assertTrue(w~items == 1, "one warning")
  self~assertTrue(w[1]~clause == "x:y~z;", "clause ["w[1]~clause"]")
  self~assertTrue(w[1]~message~pos('"(x:y)"') > 0, "message ["w[1]~message"]")
