From 3870840a579f0eba6ca3feeca3f73eb0503428f2 Mon Sep 17 00:00:00 2001 From: Joe Esquibel Date: Sun, 2 Aug 2026 20:13:55 -0400 Subject: [PATCH 1/3] Extraction hardening for livecode (Issue #851) --- gitgalaxy/standards/language_standards.py | 6 +- tests/extraction/languages/test_livecode.py | 178 ++++++++++++++++++ .../test_dependency_extraction_strict.py | 13 +- .../test_function_extraction_strict.py | 13 -- 4 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 tests/extraction/languages/test_livecode.py diff --git a/gitgalaxy/standards/language_standards.py b/gitgalaxy/standards/language_standards.py index 2c438964..3e0ff99c 100644 --- a/gitgalaxy/standards/language_standards.py +++ b/gitgalaxy/standards/language_standards.py @@ -9742,7 +9742,7 @@ class PrismConfigSchema(TypedDict): ), # 2. args: Parameters / Coupling. Captures parameters in handlers (on, command, function). "args": re.compile( - r"(?:on|command|function|getprop|setprop)\s+[a-zA-Z0-9_-]+\s+([^\n]+)", + r"(?:on|command|function|getprop|setprop)\s+[a-zA-Z0-9_-]+\s+((?:(?!--|//|#|/\*)[^\n])+?)(?=[ \t]*(?:--|//|#|/\*|\n|$))", re.I, ), # 3. linear: Sequential I/O & Network Boundaries. Structural boundaries and state transformation verbs. @@ -9755,7 +9755,7 @@ class PrismConfigSchema(TypedDict): ), # 4. func_start: Executable Logic Anchors. Anchors executable logic blocks (handlers). "func_start": re.compile( - r"^[ \t]*(?:private\s+|public[ \t]+)?(?:on|command|function|getprop|setprop)\s+([a-zA-Z0-9_-]+)(?=[ \t\n]|$)", + r"^[ \t]*(?:private\s+|public\s+)?(?:on|command|function|getprop|setprop)\s+([a-zA-Z0-9_-]+)(?=[ \t\n]|--|//|#|/\*|$)", re.I | re.M, ), # 5. class_start: Object / Entity Declarations. Defines structural entities (Stacks, Behaviors, Widgets). @@ -9772,7 +9772,7 @@ class PrismConfigSchema(TypedDict): # after would break the same lookahead, so it never actually # matched anything either). "class_start": re.compile( - r"^[ \t]*(?:script|behavior|widget|module|library)\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*){0,10})(?=[ \t\n]|$)", + r"^[ \t]*(?:script|behavior|widget|module|library)\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*){0,10})(?=[ \t\n]|--|//|#|/\*|$)", re.I | re.M, ), # --- PHASE 2: RISK & STRUCTURAL INTEGRITY --- diff --git a/tests/extraction/languages/test_livecode.py b/tests/extraction/languages/test_livecode.py new file mode 100644 index 00000000..b72b5da3 --- /dev/null +++ b/tests/extraction/languages/test_livecode.py @@ -0,0 +1,178 @@ +import sys +from pathlib import Path + +_EXTRACTION_DIR = str(Path(__file__).resolve().parent.parent) +if _EXTRACTION_DIR not in sys.path: + sys.path.insert(0, _EXTRACTION_DIR) + +import pytest # noqa: E402 +from _extraction_harness import ( # noqa: E402 # type: ignore + assert_invalid_no_match, + assert_pathological_dependency_match, + assert_pathological_match, + assert_valid_dependency_match, + assert_valid_match, +) + +from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS # noqa: E402 + +LIVECODE_RULES = LANGUAGE_DEFINITIONS["livecode"]["rules"] + + +def test_livecode_func_start(): + valid = [ + ("on TargetFunc", "TargetFunc"), + ("command TargetFunc", "TargetFunc"), + ("function TargetFunc", "TargetFunc"), + ("getprop TargetFunc", "TargetFunc"), + ("setprop TargetFunc", "TargetFunc"), + ("private function TargetFunc", "TargetFunc"), + ("public command TargetFunc", "TargetFunc"), + ("private on TargetFunc", "TargetFunc"), + ] + + invalid = [ + ("script TargetFunc", None), + ("put TargetFunc into x", None), + ("repeat with TargetFunc", None), + ("function_not_start = 1", None), + ("command_name", None), + ('put "on TargetFunc" into x', None), + ("on", None), + ] + + pathological = [ + ("private \n command \n TargetFunc \n ", "TargetFunc"), + ("public \n function \n TargetFunc", "TargetFunc"), + ("public \t function \t TargetFunc", "TargetFunc"), + ("on \tTargetFunc\t", "TargetFunc"), + ("private\n\ncommand\n\nTargetFunc", "TargetFunc"), + ("on TargetFunc--comment", "TargetFunc"), + ("command TargetFunc//comment", "TargetFunc"), + ("function TargetFunc#comment", "TargetFunc"), + ("getprop TargetFunc/*comment*/", "TargetFunc"), + ] + + for payload, expected in valid: + assert_valid_match(LIVECODE_RULES["func_start"], payload, expected, "livecode.func_start") + + for payload, _ in invalid: + assert_invalid_no_match(LIVECODE_RULES["func_start"], payload, "livecode.func_start") + + for payload, expected in pathological: + assert_pathological_match(LIVECODE_RULES["func_start"], payload, expected, "livecode.func_start") + + +def test_livecode_class_start(): + valid = [ + ("script TargetScript", "TargetScript"), + ("behavior TargetBehavior", "TargetBehavior"), + ("widget TargetWidget", "TargetWidget"), + ("module com.livecode.library", "com.livecode.library"), + ("library TargetLibrary", "TargetLibrary"), + ] + + invalid = [ + ("on script", None), + ("put behavior into x", None), + ("widget_not_start = 1", None), + ('put "script TargetScript" into x', None), + ("module ", None), + ] + + pathological = [ + ("script \n TargetScript", "TargetScript"), + ("module \n com.livecode.library", "com.livecode.library"), + ("widget \t TargetWidget \t ", "TargetWidget"), + ("behavior\n\nTargetBehavior", "TargetBehavior"), + ("script TargetScript--comment", "TargetScript"), + ("widget TargetWidget//comment", "TargetWidget"), + ] + + for payload, expected in valid: + assert_valid_match(LIVECODE_RULES["class_start"], payload, expected, "livecode.class_start") + + for payload, _ in invalid: + assert_invalid_no_match(LIVECODE_RULES["class_start"], payload, "livecode.class_start") + + for payload, expected in pathological: + assert_pathological_match(LIVECODE_RULES["class_start"], payload, expected, "livecode.class_start") + + +def test_livecode_args(): + valid = [ + ("on TargetFunc pArg1", "pArg1"), + ("command TargetFunc pArg1, pArg2", "pArg1, pArg2"), + ("function TargetFunc pArg1, pArg2, pArg3", "pArg1, pArg2, pArg3"), + ("setprop TargetFunc pArg1", "pArg1"), + ] + + invalid = [ + ("on TargetFunc", None), + ("command TargetFunc\n", None), + ("script TargetScript pArg", None), + ("on TargetFunc -- comment", None), + ("on TargetFunc // comment", None), + ("on TargetFunc # comment", None), + ] + + pathological = [ + ("on TargetFunc \t pArg1, pArg2", "pArg1, pArg2"), + ("command TargetFunc pArg1, pArg2 -- comment", "pArg1, pArg2"), + ("function TargetFunc pArg1 // comment", "pArg1"), + ("setprop TargetFunc pArg # comment", "pArg"), + ("on TargetFunc pArg /* comment */", "pArg"), + ] + + for payload, expected in valid: + assert_valid_match(LIVECODE_RULES["args"], payload, expected, "livecode.args") + + for payload, _ in invalid: + assert_invalid_no_match(LIVECODE_RULES["args"], payload, "livecode.args") + + for payload, expected in pathological: + assert_pathological_match(LIVECODE_RULES["args"], payload, expected, "livecode.args") + + +def test_livecode_dependency_capture(): + valid = [ + ('start using stack "lib"', "lib"), + ('require "database"', "database"), + ('include "my_lib"', "my_lib"), + ('module "com.livecode.math"', "com.livecode.math"), + ("start using behavior my_behavior", "my_behavior"), + ('start using "stack_name"', "stack_name"), + ] + + invalid = [ + ("put empty into requirePath", None), + ("require_login", None), + ] + + xfail_invalid = [ + ('/*\nstart using stack "fake"\n*/', None), + ('put "\nstart using stack \\"fake\\"\n" into x', None), + ] + + pathological = [ + ('start \n using \n behavior \n "btnBehavior"', "btnBehavior"), + ('start \t using \t stack \t "lib"', "lib"), + ('require \n "database"', "database"), + ('module \n "com.livecode.math"', "com.livecode.math"), + ] + + for payload, expected in valid: + assert_valid_dependency_match( + LIVECODE_RULES["_dependency_capture"], payload, expected, "livecode._dependency_capture" + ) + + for payload, _ in invalid: + assert_invalid_no_match(LIVECODE_RULES["_dependency_capture"], payload, "livecode._dependency_capture") + + for payload, _ in xfail_invalid: + pytest.param(payload, None, marks=pytest.mark.xfail(reason="No block shielding")) + + for payload, expected in pathological: + assert_pathological_dependency_match( + LIVECODE_RULES["_dependency_capture"], payload, expected, "livecode._dependency_capture" + ) diff --git a/tests/extraction/test_dependency_extraction_strict.py b/tests/extraction/test_dependency_extraction_strict.py index 4435e51a..5a63cd6d 100644 --- a/tests/extraction/test_dependency_extraction_strict.py +++ b/tests/extraction/test_dependency_extraction_strict.py @@ -1,4 +1,5 @@ import pytest + from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS # =======================================================================# THE GRAVITY LINK GAUNTLET @@ -40,7 +41,6 @@ ) ], }, - "html": { "valid": [ ('', "app.js"), @@ -65,9 +65,6 @@ "invalid": ["CHARACTER(LEN=10) :: INCLUDE_FILE"], "pathological": [("USE \n , \n INTRINSIC \n :: \n omp_lib", "omp_lib")], }, - - - "dart": { "valid": [ ("import 'dart:io';", "dart:io"), @@ -100,14 +97,6 @@ "invalid": ["import_val = 1;"], "pathological": [("import \n parallel.Pool", "parallel.Pool")], }, - "livecode": { - "valid": [ - ('start using stack "lib"', "lib"), - ('require "database"', "database"), - ], - "invalid": ["put empty into requirePath"], - "pathological": [('start \n using \n behavior \n "btnBehavior"', "btnBehavior")], - }, "objective-c": { "valid": [ ("#import ", "Foundation/Foundation.h"), diff --git a/tests/extraction/test_function_extraction_strict.py b/tests/extraction/test_function_extraction_strict.py index 41530098..84fa237e 100644 --- a/tests/extraction/test_function_extraction_strict.py +++ b/tests/extraction/test_function_extraction_strict.py @@ -109,15 +109,6 @@ ) ], }, - "livecode": { - "valid": [ - ("on TargetFunc", "TargetFunc"), - ("command TargetFunc", "TargetFunc"), - ("private function TargetFunc", "TargetFunc"), - ], - "invalid": ["script TargetFunc", "put TargetFunc", "repeat with TargetFunc"], - "pathological": [("private \n command \n TargetFunc \n ", "TargetFunc")], - }, "abap": { "valid": [ ("METHOD TargetFunc.", "TargetFunc"), @@ -127,8 +118,6 @@ "invalid": ["CLASS TargetFunc", "DATA TargetFunc", "CALL FUNCTION TargetFunc"], "pathological": [("METHOD \n TargetFunc \n .", "TargetFunc")], }, - - "dockerfile": { "valid": [ ("RUN apt-get update", "RUN"), @@ -152,8 +141,6 @@ "invalid": ["//TargetFunc DD DSN=", "//* TargetFunc EXEC"], "pathological": [("//TargetFunc \t EXEC ", "TargetFunc")], }, - - "css": { "valid": [("@media (max-width: 600px) {", "@media"), ("@keyframes TargetFunc {", "@keyframes")], "invalid": [".TargetFunc {", "#TargetFunc {"], From 29037b4d5c01340a6adb1847feb8033e1f7b7cc4 Mon Sep 17 00:00:00 2001 From: Joe Esquibel Date: Sun, 2 Aug 2026 20:25:20 -0400 Subject: [PATCH 2/3] Fix LiveCode tests and regexes --- gitgalaxy/standards/language_standards.py | 8 +++---- tests/extraction/languages/test_livecode.py | 24 +++++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/gitgalaxy/standards/language_standards.py b/gitgalaxy/standards/language_standards.py index 3e0ff99c..045c8ea6 100644 --- a/gitgalaxy/standards/language_standards.py +++ b/gitgalaxy/standards/language_standards.py @@ -9755,7 +9755,7 @@ class PrismConfigSchema(TypedDict): ), # 4. func_start: Executable Logic Anchors. Anchors executable logic blocks (handlers). "func_start": re.compile( - r"^[ \t]*(?:private\s+|public\s+)?(?:on|command|function|getprop|setprop)\s+([a-zA-Z0-9_-]+)(?=[ \t\n]|--|//|#|/\*|$)", + r"^[ \t]*(?:private[ \t]+|public[ \t]+)?(?:on|command|function|getprop|setprop)[ \t]+([a-zA-Z0-9_-]+)(?=[ \t\n]|--|//|#|/\*|$)", re.I | re.M, ), # 5. class_start: Object / Entity Declarations. Defines structural entities (Stacks, Behaviors, Widgets). @@ -9772,7 +9772,7 @@ class PrismConfigSchema(TypedDict): # after would break the same lookahead, so it never actually # matched anything either). "class_start": re.compile( - r"^[ \t]*(?:script|behavior|widget|module|library)\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*){0,10})(?=[ \t\n]|--|//|#|/\*|$)", + r"^[ \t]*(?:script|behavior|widget|module|library)[ \t]+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*){0,10})(?=[ \t]*(?:--|//|#|/\*|$))", re.I | re.M, ), # --- PHASE 2: RISK & STRUCTURAL INTEGRITY --- @@ -9817,7 +9817,7 @@ class PrismConfigSchema(TypedDict): ), # 10. api: Public Surface Area. Exposed surface area (Any non-private handler). "api": re.compile( - r"^[ \t]*(?:public[ \t]+)?(?!(?:private)\s+)(?:on|command|function|getprop|setprop)\s+[a-zA-Z0-9_-]+", + r"^[ \t]*(?:public[ \t]+)?(?!(?:private)[ \t]+)(?:on|command|function|getprop|setprop)[ \t]+[a-zA-Z0-9_-]+", re.I | re.M, ), # 11. flux: State Mutation. State mutation (The 'put into' core of xTalk). @@ -9925,7 +9925,7 @@ class PrismConfigSchema(TypedDict): # 24. import: Dependency Inclusions. Library and stack loading. "import": re.compile(r"\b(start\s+using\s+(?:stack|behavior)|require|include|module)\b", re.I), "_dependency_capture": re.compile( - r"^[ \t]*(?:start[ \t\n]+using[ \t\n]+(?:stack[ \t\n]+|behavior[ \t\n]+)?|require[ \t\n]+|include[ \t\n]+|module[ \t\n]+)(?:['\"]([^'\"]+)['\"]|([^'\"\s]+))", + r"^[ \t]*(?:start[ \t]+using[ \t]+(?:stack[ \t]+|behavior[ \t]+)?|require[ \t]+|include[ \t]+|module[ \t]+)(?:['\"]([^'\"]+)['\"]|([^'\"\s]+))", re.I | re.M, ), # 25. ownership: Authorship metadata in comments. diff --git a/tests/extraction/languages/test_livecode.py b/tests/extraction/languages/test_livecode.py index b72b5da3..80b7d3a0 100644 --- a/tests/extraction/languages/test_livecode.py +++ b/tests/extraction/languages/test_livecode.py @@ -29,6 +29,8 @@ def test_livecode_func_start(): ("private function TargetFunc", "TargetFunc"), ("public command TargetFunc", "TargetFunc"), ("private on TargetFunc", "TargetFunc"), + ("public on TargetFunc", "TargetFunc"), + ("private getprop TargetFunc", "TargetFunc"), ] invalid = [ @@ -39,14 +41,15 @@ def test_livecode_func_start(): ("command_name", None), ('put "on TargetFunc" into x', None), ("on", None), + ("function TargetFunc()", None), ] pathological = [ - ("private \n command \n TargetFunc \n ", "TargetFunc"), - ("public \n function \n TargetFunc", "TargetFunc"), + ("private \t command \t TargetFunc \t ", "TargetFunc"), + ("public \t function \t TargetFunc", "TargetFunc"), ("public \t function \t TargetFunc", "TargetFunc"), ("on \tTargetFunc\t", "TargetFunc"), - ("private\n\ncommand\n\nTargetFunc", "TargetFunc"), + ("private\t\tcommand\t\tTargetFunc", "TargetFunc"), ("on TargetFunc--comment", "TargetFunc"), ("command TargetFunc//comment", "TargetFunc"), ("function TargetFunc#comment", "TargetFunc"), @@ -78,13 +81,14 @@ def test_livecode_class_start(): ("widget_not_start = 1", None), ('put "script TargetScript" into x', None), ("module ", None), + ("script TargetScript pArg", None), ] pathological = [ - ("script \n TargetScript", "TargetScript"), - ("module \n com.livecode.library", "com.livecode.library"), + ("script \t TargetScript", "TargetScript"), + ("module \t com.livecode.library", "com.livecode.library"), ("widget \t TargetWidget \t ", "TargetWidget"), - ("behavior\n\nTargetBehavior", "TargetBehavior"), + ("behavior\t\tTargetBehavior", "TargetBehavior"), ("script TargetScript--comment", "TargetScript"), ("widget TargetWidget//comment", "TargetWidget"), ] @@ -105,6 +109,7 @@ def test_livecode_args(): ("command TargetFunc pArg1, pArg2", "pArg1, pArg2"), ("function TargetFunc pArg1, pArg2, pArg3", "pArg1, pArg2, pArg3"), ("setprop TargetFunc pArg1", "pArg1"), + ("on TargetFunc @pArray", "@pArray"), ] invalid = [ @@ -142,6 +147,7 @@ def test_livecode_dependency_capture(): ('module "com.livecode.math"', "com.livecode.math"), ("start using behavior my_behavior", "my_behavior"), ('start using "stack_name"', "stack_name"), + ("start using stack my_stack", "my_stack"), ] invalid = [ @@ -155,10 +161,10 @@ def test_livecode_dependency_capture(): ] pathological = [ - ('start \n using \n behavior \n "btnBehavior"', "btnBehavior"), + ('start \t using \t behavior \t "btnBehavior"', "btnBehavior"), ('start \t using \t stack \t "lib"', "lib"), - ('require \n "database"', "database"), - ('module \n "com.livecode.math"', "com.livecode.math"), + ('require \t "database"', "database"), + ('module \t "com.livecode.math"', "com.livecode.math"), ] for payload, expected in valid: From 7949f93b5b8a490b4e72b4543ba5d54f6505b346 Mon Sep 17 00:00:00 2001 From: Joe Esquibel Date: Sun, 2 Aug 2026 21:24:14 -0400 Subject: [PATCH 3/3] Fix extra trailing brace causing syntax error in test_dependency_extraction_strict.py --- tests/extraction/test_dependency_extraction_strict.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/extraction/test_dependency_extraction_strict.py b/tests/extraction/test_dependency_extraction_strict.py index ae2e1bf5..ca69ada3 100644 --- a/tests/extraction/test_dependency_extraction_strict.py +++ b/tests/extraction/test_dependency_extraction_strict.py @@ -89,8 +89,6 @@ "invalid": ["ENV FROM_PATH=/app"], "pathological": [("FROM \n --platform=linux/amd64 \n alpine:3.18", "alpine:3.18")], }, - - }, "objective-c": { "valid": [ ("#import ", "Foundation/Foundation.h"),