Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions docs/unreferenced_by_name_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,15 @@ Seven corollaries, each pinned by a test in

Both default to the old reading, so a language that declares neither is unchanged by
construction. Declaring one is a measured behaviour change, not a tidy-up: it moves the census
for that language and needs its own corpus evidence. **cobol is the only language that declares
either today** — the other case-insensitive languages (fortran, pli, rexx, hlasm, abap, and the
sql family) have the same defect latent and are audited per language in #3225, not bulk-edited.
for that language and needs its own corpus evidence. cobol declared both first (#3198). #3225
audited the other case-insensitive by-name languages one at a time and declared
`identifier_case: "insensitive"` for fortran, pli, rexx, hlasm, abap, powershell, ada, apex and
livecode (none needs extra name characters). Across the crucible and keyword-rosetta that moved
10 of 3,043 units from unreferenced to referenced: 7 genuine calls written in another case, and
3 only by a word in a string literal (Ada `Between`/`Clone`, Fortran `lsm`) — corollary 3's
literal limit, which folding exposes but does not introduce. Not declared: batch (no crucible
unit to measure), the positional family (bms, sqlite, db2_sql; no census, the key would be
inert), and haskell, whose `case_insensitive_imports` is about modules, not identifiers.

`kind` is a **census** over the extracted function population and its `unit` is **functions** —
not hits, and not lines. A formula that adds it to a rule's hit count is adding unlike things;
Expand Down
5 changes: 5 additions & 0 deletions gitgalaxy/standards/language_standards/languages/abap.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
# for an asterisk '*' to identify line-level Commented / Non-Executable Text, while allowing '"' for inline.
# ABAP code uses " as inline comment, requiring a specialized positional family
"lexical_family": "positional_abap",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). ABAP names are case-insensitive
# (`PERFORM probe_branch` reaches `FORM PROBE_BRANCH`). Measured: no crucible or
# keyword-rosetta unit moves. TOP LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": re.compile(r"(?i)\b(?:PERFORM|CALL\s+(?:FUNCTION|METHOD|TRANSACTION))\s+'?([A-Za-z_/][\w/]*)"),
Expand Down
7 changes: 7 additions & 0 deletions gitgalaxy/standards/language_standards/languages/ada.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@
# `--` delimiter, no block form) -- see gitgalaxy_config.py's
# LEXICAL_FAMILY_HEURISTICS and prism.py's _compile_regex_matrix.
"lexical_family": "line_exclusive_dash",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). Ada identifiers are
# case-insensitive (RM 2.3). Measured: two crucible alire units now read as referenced, both
# only by an English word in a string literal (`"No changes between ..."`, `"clone"`) -- the
# stated corollary-3 limit (#2535: nothing shields literals), which folding exposes but does
# not introduce. TOP LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
Expand Down
5 changes: 5 additions & 0 deletions gitgalaxy/standards/language_standards/languages/apex.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
"shebangs": [],
# Rationale: Uses standard '//' for lines and '/*' '*/' for block-level Commented / Non-Executable Text.
"lexical_family": "standard_block",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). Apex identifiers are
# case-insensitive. Measured: no crucible or keyword-rosetta unit moves. TOP LEVEL, never
# inside `rules` (#2806).
"identifier_case": "insensitive",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_C_STYLE,
Expand Down
9 changes: 9 additions & 0 deletions gitgalaxy/standards/language_standards/languages/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
# UPGRADED: Maps to Family 7 (The Positional Ancients)
# Rationale: Fixed-format requires Column 1 monitoring ('C' or '*'); Free-format uses '!'.
"lexical_family": "positional_anchored",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). Fortran names are
# case-insensitive (FORTRAN 77 onward). Measured: crucible wrf/module_domain.F calls
# `domain_ClockIsSimStartTime(grid)` for `domain_clockissimstarttime`, which the
# case-sensitive test read as uncalled; wrf/module_sf_noahdrv.F's `lsm` now reads as
# referenced only by a string literal (`'... Noah LSM RELATED TABLES'`), the stated
# corollary-3 limit (#2535) that folding exposes but does not introduce. TOP LEVEL, never
# inside `rules` (#2806).
"identifier_case": "insensitive",
# #2540: Fortran resolves module names case-insensitively (`USE A` and
# `use a` both bind module a). The dependency DAG's import-token ->
# file lookup (network_risk_sensor.py) must case-fold for this
Expand Down
5 changes: 5 additions & 0 deletions gitgalaxy/standards/language_standards/languages/hlasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
# 'C'/'c'/'/'/'!' would erase any real statement whose column-1 name field
# starts with one of them (`CHECKPT LR ...` -- the #1898 shape).
"lexical_family": "positional_anchored",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). HLASM folds lower-case symbols
# to upper case (COMPAT(NOCASE), the default). Measured: no crucible or keyword-rosetta unit
# moves. TOP LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
# COPY members resolve to PDS members, which are case-insensitive.
"case_insensitive_imports": True,
# #3199: this language's import statement names a library MEMBER that the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
# UPGRADED: Maps to Family 1d (multi_style_live)
# Rationale: Accepts '--', '//', '#', and '/* */' to support both its legacy HyperTalk roots and modern C-style syntax.
"lexical_family": "multi_style_live",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). LiveCode (xTalk) handler names
# are case-insensitive. Measured: four crucible handlers read as uncalled although called in
# another case, e.g. engine_builder's `engineBuilderBuildiOS` for `engineBuilderBuildIOS`. TOP
# LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_COMMAND_POSITION,
Expand Down
5 changes: 5 additions & 0 deletions gitgalaxy/standards/language_standards/languages/pli.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@
# It is NOT positional_anchored like COBOL: PL/I's margins (2-72) are a compiler
# option, not a column-indicator comment syntax.
"lexical_family": "standard_block",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). PL/I names are case-insensitive.
# Measured: no crucible or keyword-rosetta unit moves -- every call there is written in its
# declaration's case. TOP LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
# #3250: the named record-layout fact channel. PL/I DECLAREd structures (a
# level-number hierarchy of named items with attributes) are the analog of a
# COBOL DATA DIVISION record layout and feed the same `record_data` table via
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
# Rationale: PowerShell uses '#' for single-line comments but relies on
# a unique '<# #>' syntax for multi-line block comments, requiring hybrid parsing logic.
"lexical_family": "embedded_syntax",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). PowerShell command and function
# names are case-insensitive. Measured: crucible roslyn/build-utils.ps1 calls
# `Ensure-DotNetSdk` for `Ensure-DotnetSdk`, which the case-sensitive test read as uncalled.
# TOP LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
"rules": {
# Epic #3264: Explicitly declare the structural invocation paradigm
"calls_out": CALLS_OUT_COMMAND_POSITION,
Expand Down
6 changes: 6 additions & 0 deletions gitgalaxy/standards/language_standards/languages/rexx.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
# in real source) and REXX quote morphology (quotes double to escape, no
# backslash escapes, strings never span lines).
"lexical_family": "recursive_block_rexx",
# #3225: the identifier lexicon of the `unreferenced_by_name` census
# (docs/unreferenced_by_name_contract.md corollary 7, #3198). REXX symbols are
# case-insensitive (they fold to upper case). Measured: crucible ibm_z_zos/domchk.rexx calls
# `Process_DOM_Main()` for `PROCESS_DOM_MAIN`, which the case-sensitive test read as uncalled.
# TOP LEVEL, never inside `rules` (#2806).
"identifier_case": "insensitive",
# External execs and ::REQUIRES members resolve to PDS members / host
# filesystems where case is not significant.
"case_insensitive_imports": True,
Expand Down
80 changes: 75 additions & 5 deletions tests/core_engine/test_unreferenced_by_name_contract_2806.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import pytest

from gitgalaxy.core.detector import (
IDENTIFIER_CASE_INSENSITIVE,
IDENTIFIER_CASE_SENSITIVE,
IDENTIFIER_CASES,
INVOCATION_BY_NAME,
Expand Down Expand Up @@ -344,13 +345,29 @@ def test_exactly_the_declared_languages_carry_a_lexicon():
language keeps the case-sensitive, `\\w`-only reading and is unchanged by
construction (#3198).

cobol is first because the refraction pipeline reads its census; the other
case-insensitive languages (fortran, pli, rexx, hlasm, abap, ...) are audited
per language in #3225 -- declaring one is a behaviour change that needs its
own corpus evidence, not a bulk edit.
cobol was first because the refraction pipeline reads its census; #3225
audited the other case-insensitive by-name languages one at a time against the
crucible and keyword-rosetta and declared `identifier_case` for each (evidence
in each registry's comment). Only cobol declares extra name characters.
Deliberately absent: batch (no crucible unit to measure), the positional
family (bms, sqlite, db2_sql -- no census, the key would be inert), and
haskell (`case_insensitive_imports` is about module resolution; its
identifiers are case-sensitive).
"""
declared = {lang for lang in LANGUAGE_DEFINITIONS if _lexicon(lang) != (IDENTIFIER_CASE_SENSITIVE, "")}
assert declared == {"cobol"}
assert declared == {
"cobol",
"fortran",
"pli",
"rexx",
"hlasm",
"abap",
"powershell",
"ada",
"apex",
"livecode",
}
assert {lang for lang in declared if _lexicon(lang)[1]} == {"cobol"}


def test_a_case_insensitive_language_sees_a_differently_cased_call():
Expand Down Expand Up @@ -395,3 +412,56 @@ def test_a_hyphen_is_still_a_boundary_for_a_language_that_declares_nothing():
the way it always did, because scheme declares no extra name characters."""
case, extra = _lexicon("scheme")
assert (case, extra) == (IDENTIFIER_CASE_SENSITIVE, "")


# #3225: each case-insensitive language, on the crucible shape that proved it.
# The unit is written in one case and called in another; the case-sensitive
# default read every one of these as unreferenced.
_CASE_FOLD_SHAPES = {
# wrf/module_domain.F: `domain_ClockIsSimStartTime(grid)` for `domain_clockissimstarttime`.
"fortran": (
" PROGRAM MAIN\n LOGICAL S\n S = Clock_Is_Start(1)\n END PROGRAM MAIN\n\n"
" LOGICAL FUNCTION clock_is_start(G)\n INTEGER G\n clock_is_start = .TRUE.\n"
" END FUNCTION clock_is_start\n",
"clock_is_start",
),
# ibm_z_zos/domchk.rexx: `Process_DOM_Main()` for `PROCESS_DOM_MAIN`.
"rexx": (
"/* REXX */\nrc = Process_Dom_Main()\nexit rc\n\nPROCESS_DOM_MAIN:\n say 'main'\n return 0\n",
"PROCESS_DOM_MAIN",
),
# roslyn/build-utils.ps1: `Ensure-DotNetSdk` for `Ensure-DotnetSdk`.
"powershell": ("function Ensure-DotnetSdk {\n return 1\n}\n\n$dotnet = Ensure-DotNetSdk\n", "Ensure-DotnetSdk"),
# livecode/engine_builder.livecodescript: `engineBuilderBuildiOS` for `engineBuilderBuildIOS`.
"livecode": (
"on mouseUp\n engineBuilderBuildiOS 1, 2\nend mouseUp\n\n"
"command engineBuilderBuildIOS pEdition, pVersion\n put pEdition into tX\nend engineBuilderBuildIOS\n",
"engineBuilderBuildIOS",
),
"ada": (
"package body Probe is\n procedure Run is\n begin\n clone_repo;\n end Run;\n\n"
" procedure Clone_Repo is\n begin\n null;\n end Clone_Repo;\nend Probe;\n",
"Clone_Repo",
),
}


@pytest.mark.parametrize("lang", sorted(_CASE_FOLD_SHAPES))
def test_a_call_in_another_case_is_a_reference(lang):
"""With the declaration the call counts; with the key stripped the same code
reads the unit as unreferenced -- so the declaration is what does the work."""
from gitgalaxy.core.prism import Prism
from gitgalaxy.standards.gitgalaxy_config import LEXICAL_FAMILY_HEURISTICS

source, unit = _CASE_FOLD_SHAPES[lang]
code = Prism(LEXICAL_FAMILY_HEURISTICS, LANGUAGE_DEFINITIONS).split_streams(source, lang)["code_stream"]

def status(registry):
functions = StructuralExtractor(lang, registry).splice(code, "")["functions"]
return {f["name"]: f["usage_status"] for f in functions}[unit]

assert _lexicon(lang) == (IDENTIFIER_CASE_INSENSITIVE, "")
assert status(LANGUAGE_DEFINITIONS) == 0
stripped = dict(LANGUAGE_DEFINITIONS)
stripped[lang] = {k: v for k, v in LANGUAGE_DEFINITIONS[lang].items() if k != "identifier_case"}
assert status(stripped) == 1
Loading
Loading