add docs and AI skills
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
name: evolve-eftl-linter
|
||||||
|
description: Evolve the EFTL VS Code linter and language server while keeping tokenizer, parser, LSP diagnostics, navigation, TextMate grammar, editor configuration, snippets, examples, and documentation consistent. Use for EFTL feature implementation, bug fixes, refactors, new diagnostics, language-server capabilities, or support for a newly documented EFTL construct.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Evolve EFTL Linter
|
||||||
|
|
||||||
|
Implement the smallest coherent change across all affected language-support layers. Read [references/project-architecture.md](references/project-architecture.md) before a non-trivial change.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Inspect the worktree and preserve unrelated user changes.
|
||||||
|
2. Convert the request into a language rule card. Use `$interpret-eftl-language` for new or disputed EFTL behavior.
|
||||||
|
3. Search all support surfaces for the construct and record the required files.
|
||||||
|
4. Add or update focused tests and fixtures before broad refactoring. If test infrastructure is absent, introduce the smallest repository-level harness that exercises tokenizer and parser without VS Code.
|
||||||
|
5. Change recognition in `src/tokenizer.ts`. Preserve exact source spans and make a deliberate decision about case-insensitivity and malformed delimiters.
|
||||||
|
6. Change structure in `src/parser.ts`. Validate grammar relationships and ordering, not only matching close tags. Recover after an error so one defect does not flood the document with misleading diagnostics.
|
||||||
|
7. Change semantic analysis and LSP behavior in `src/server.ts`. Keep internal positions unambiguous and convert to zero-based LSP ranges only at the boundary.
|
||||||
|
8. Synchronize editor assets when user-visible syntax changes: TextMate grammar, language configuration, snippets, examples, and README.
|
||||||
|
9. Run `$verify-eftl-linter`; inspect every failure and any new diagnostic range manually.
|
||||||
|
|
||||||
|
## Design constraints
|
||||||
|
|
||||||
|
- Do not encode the same tag metadata independently in several new switch statements. Prefer a shared declarative definition when a change spans tokenization, parsing, and validation.
|
||||||
|
- Keep parsing separate from LSP transport so core behavior is testable without starting a language client.
|
||||||
|
- Do not claim full expression validation until the grammar supports precedence, literals, operators, and recovery defined by the runtime language.
|
||||||
|
- Avoid static errors for values known only at runtime. Use warnings only when they are actionable and low-noise.
|
||||||
|
- Cap published diagnostics with `eftl.maxNumberOfProblems` and make truncation deterministic when implementing server work.
|
||||||
|
- Preserve valid user text and ElixForms TAG payloads; brackets inside comments, expressions, quoted attributes, or TAG bodies need context-aware handling.
|
||||||
|
- Guarantee scanner progress: every tokenization step must consume input or terminate with an error. Include unknown and incomplete `[` constructs in regression tests.
|
||||||
|
- Treat diagnostic ranges as first-class behavior. Test single-line and multiline tokens, CRLF and LF, and non-ASCII text before the error.
|
||||||
|
|
||||||
|
## Change-surface checklist
|
||||||
|
|
||||||
|
| Concern | Primary files |
|
||||||
|
| --- | --- |
|
||||||
|
| Token kinds, scanning, locations | `src/tokenizer.ts` |
|
||||||
|
| Nesting, ordering, recovery, AST | `src/parser.ts` |
|
||||||
|
| Diagnostics, config, symbols, definitions | `src/server.ts` |
|
||||||
|
| Client activation | `src/extension.ts`, `package.json` |
|
||||||
|
| Highlighting | `syntaxes/eftl.tmLanguage.json` |
|
||||||
|
| Brackets, comments, folding | `language-configuration.json` |
|
||||||
|
| Authoring examples | `snippets/eftl.json`, `examples/`, `README.md` |
|
||||||
|
| Build and tests | `package.json`, `tsconfig.json`, test files |
|
||||||
|
|
||||||
|
Update only surfaces affected by the rule card, but explicitly state why an apparently related surface is unchanged.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Evolve EFTL Linter"
|
||||||
|
short_description: "Implement coordinated EFTL linter changes"
|
||||||
|
default_prompt: "Use $evolve-eftl-linter to implement this EFTL language-support change safely."
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Project architecture and evolution notes
|
||||||
|
|
||||||
|
## Runtime flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
.eftl document
|
||||||
|
-> VS Code language client (`src/extension.ts`)
|
||||||
|
-> LSP server (`src/server.ts`)
|
||||||
|
-> tokenizer (`src/tokenizer.ts`)
|
||||||
|
-> stack parser (`src/parser.ts`)
|
||||||
|
-> diagnostics and per-document variable definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
Separately, VS Code loads the TextMate grammar, language configuration, and snippets directly from `package.json` contributions.
|
||||||
|
|
||||||
|
## Current responsibilities
|
||||||
|
|
||||||
|
- `src/extension.ts`: starts `out/server.js` over IPC for file-backed EFTL documents.
|
||||||
|
- `src/tokenizer.ts`: recognizes a fixed, mostly uppercase set of tags, expressions, comments, and plain text; stores one-based line and column plus a character length.
|
||||||
|
- `src/parser.ts`: checks stack-balanced open and close token kinds. It declares an `AstNode` interface but does not currently build an AST or enforce IF/WHILE child ordering.
|
||||||
|
- `src/server.ts`: tokenizes and parses on document changes, validates VAR `type`, collects variable declarations, publishes diagnostics, and implements same-document go-to-definition by word matching.
|
||||||
|
- `syntaxes/eftl.tmLanguage.json`: highlights a broader syntax independently of the TypeScript parser.
|
||||||
|
- `language-configuration.json`: defines comment, bracket, auto-close, surrounding-pair, and folding behavior.
|
||||||
|
- `snippets/eftl.json` and `examples/sample.eftl`: provide authoring examples and useful regression inputs.
|
||||||
|
|
||||||
|
## Baseline gaps to re-check
|
||||||
|
|
||||||
|
These observations describe the repository when this skill was created and are not permanent requirements:
|
||||||
|
|
||||||
|
- Documented `LOG`, `FOR`, `IS_EMPTY`, and `IS_NOT_EMPTY` constructs are not tokenized or structurally parsed.
|
||||||
|
- The specification describes case-insensitive tags; tokenization currently uses exact uppercase matches.
|
||||||
|
- An unrecognized `[` currently makes `scanText()` return without consuming input, so lowercase, unknown, or incomplete tags can trap tokenization in an infinite loop.
|
||||||
|
- Prefix checks such as `[VAR` need a tag-name boundary; otherwise longer unknown names can be misclassified.
|
||||||
|
- WHILE attributes and the attribute form of TRIM are not recognized by the current exact scanners.
|
||||||
|
- Parser validation is balance-only; it does not enforce root scope, IF branch ordering/cardinality, WHILE shape, or allowed bodies.
|
||||||
|
- Attribute parsing uses regular expressions in the server and validates only VAR type.
|
||||||
|
- Source positions combine one-based line/column with token string length. Multiline diagnostic end ranges and variable-definition selections require care.
|
||||||
|
- `eftl.maxNumberOfProblems` is declared but not consumed by the server.
|
||||||
|
- No automated test script is declared.
|
||||||
|
- `npm run compile` passes at the baseline. `npm run lint` fails before linting because ESLint 9 cannot find a flat `eslint.config.*` file.
|
||||||
|
|
||||||
|
## Preferred direction
|
||||||
|
|
||||||
|
Evolve incrementally toward:
|
||||||
|
|
||||||
|
1. a source-span model based on offsets with reliable LSP conversion;
|
||||||
|
2. a context-aware scanner that can report malformed input and recover;
|
||||||
|
3. structured attribute parsing with ranges;
|
||||||
|
4. an AST or equivalent parse structure that represents documented relationships;
|
||||||
|
5. separate semantic passes for declarations, references, types, and TAG payloads;
|
||||||
|
6. table-driven language metadata shared where doing so reduces drift;
|
||||||
|
7. core unit tests plus a small number of LSP integration tests.
|
||||||
|
|
||||||
|
Do not perform this redesign wholesale for an unrelated small fix. Introduce seams that make the next supported construct easier and safer.
|
||||||
|
|
||||||
|
## Completion criteria for a language construct
|
||||||
|
|
||||||
|
A construct is complete only when applicable layers agree on:
|
||||||
|
|
||||||
|
- accepted spellings and delimiters;
|
||||||
|
- attribute names and body form;
|
||||||
|
- nesting and ordering;
|
||||||
|
- malformed-input recovery;
|
||||||
|
- diagnostic message, severity, and exact range;
|
||||||
|
- highlighting and folding;
|
||||||
|
- snippet/example syntax;
|
||||||
|
- focused positive, negative, boundary, and regression tests.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
name: interpret-eftl-language
|
||||||
|
description: Interpret the EFTL language specification and turn the repository PDF documentation into explicit lexical, syntactic, structural, attribute, and semantic rules. Use when adding or reviewing EFTL tags, expressions, nesting constraints, ElixForms TAG payloads, examples, diagnostics, or when the documentation and current TypeScript implementation disagree.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Interpret EFTL Language
|
||||||
|
|
||||||
|
Derive a small, testable language contract before changing implementation code.
|
||||||
|
|
||||||
|
## Establish the source of truth
|
||||||
|
|
||||||
|
Use the sources in this order:
|
||||||
|
|
||||||
|
1. Read the relevant section of `docs/elixForms_Doc_EFTL.pdf` for EFTL behavior and examples.
|
||||||
|
2. Read `docs/elixForms_EFTLParser_syntax_rels.pdf` for parser relationships and allowed composition.
|
||||||
|
3. Read `docs/elixForms_TAG_Sintassi.pdf` for positional `SCHEMAID` and `GETVALUEBYTAG` payloads.
|
||||||
|
4. Use [references/eftl-language-reference.md](references/eftl-language-reference.md) as a navigation aid and concise baseline, never as a replacement for a disputed PDF passage.
|
||||||
|
5. Treat `src/`, `syntaxes/`, `snippets/`, `examples/`, and `README.md` as the current implementation, not as normative language documentation.
|
||||||
|
|
||||||
|
If the PDFs contradict one another or leave a rule unclear, report the ambiguity and preserve it in tests or design notes. Do not silently infer a restrictive diagnostic.
|
||||||
|
|
||||||
|
## Produce a rule card
|
||||||
|
|
||||||
|
For every construct being changed, record:
|
||||||
|
|
||||||
|
- spelling and case-sensitivity;
|
||||||
|
- block, bodyless, expression, or comment form;
|
||||||
|
- required and optional attributes, value types, defaults, and mutual exclusions;
|
||||||
|
- allowed body and parent/child relationships;
|
||||||
|
- variable scope, result type, and runtime behavior relevant to static analysis;
|
||||||
|
- valid, invalid, and boundary examples;
|
||||||
|
- the safest diagnostic when a property cannot be proven statically.
|
||||||
|
|
||||||
|
Distinguish syntax errors from semantic warnings. Runtime-only facts such as external TAG values must not become false-positive syntax errors.
|
||||||
|
|
||||||
|
## Reconcile with the implementation
|
||||||
|
|
||||||
|
Search every support surface before proposing a change:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "CONSTRUCT|TokenType" src syntaxes snippets examples README.md language-configuration.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare the rule card with:
|
||||||
|
|
||||||
|
- `src/tokenizer.ts` for recognition, source positions, and case handling;
|
||||||
|
- `src/parser.ts` for nesting and ordering;
|
||||||
|
- `src/server.ts` for semantic diagnostics and symbols;
|
||||||
|
- `syntaxes/eftl.tmLanguage.json` for highlighting;
|
||||||
|
- `language-configuration.json` for brackets, folding, and comments;
|
||||||
|
- `snippets/eftl.json`, `examples/`, and `README.md` for user-facing syntax.
|
||||||
|
|
||||||
|
Return a change-surface list and explicit acceptance cases. Hand implementation work to `$evolve-eftl-linter` and validation work to `$verify-eftl-linter` when those skills are available.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Interpret EFTL Language"
|
||||||
|
short_description: "Interpret EFTL syntax and semantic rules"
|
||||||
|
default_prompt: "Use $interpret-eftl-language to derive the EFTL rules needed for this change."
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# EFTL language reference
|
||||||
|
|
||||||
|
This is a compact index derived from the documents currently under `docs/`. Re-check the PDFs when exact wording, diagrams, or edge behavior matters.
|
||||||
|
|
||||||
|
## Documentation map
|
||||||
|
|
||||||
|
- `elixForms_Doc_EFTL.pdf`: EFTL specification through the documented 3.1.0 changes, language behavior, attributes, control flow, and examples.
|
||||||
|
- `elixForms_EFTLParser_syntax_rels.pdf`: generated lexer/parser relationship diagrams.
|
||||||
|
- `elixForms_TAG_Sintassi.pdf`: positional syntax for the ElixForms `SCHEMAID` and `GETVALUEBYTAG` plugins.
|
||||||
|
|
||||||
|
## Common lexical and structural rules
|
||||||
|
|
||||||
|
- EFTL uses square-bracket tags and requires properly nested, non-overlapping elements.
|
||||||
|
- A bodyless element ends with `/]`; a block has matching opening and closing tags.
|
||||||
|
- Attribute values are quoted. Attribute order is not semantically significant.
|
||||||
|
- The main specification says uppercase and lowercase tag spellings are interpreted equally and recommends lowercase style. The current implementation is largely uppercase-only.
|
||||||
|
- Text outside `[EFTL]...[/EFTL]` is not processed. Text inside an EFTL root but outside an executable tag is emitted as user text where the grammar permits it.
|
||||||
|
- Comments use `[!-- ... --]` and may span lines.
|
||||||
|
- The generated relationships include user text, ignored whitespace, CDATA, headers, comments, statements, code blocks, and output blocks. Consult the diagram PDF before enforcing a new parent/child restriction.
|
||||||
|
|
||||||
|
## Root, directives, and context
|
||||||
|
|
||||||
|
| Construct | Form | Key rules |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| EFTL | `[EFTL] ... [/EFTL]` | Root block; no documented attributes; a document needs at least one root to be processed. |
|
||||||
|
| HEADER | `[HEADER name="..." value="..." type="..." /]` | Declares a directive. The documented directive is `trimDocument`, with boolean `true` or `false`. |
|
||||||
|
| LOG | `[LOG] ... [/LOG]` | Writes evaluated content to the server log; no documented attributes. |
|
||||||
|
|
||||||
|
Predefined execution-context names include `currentDateTime`, `defaultLocale`, `currentLocale`, and `requestId`. Other values may be supplied by the calling service.
|
||||||
|
|
||||||
|
## Variables and value functions
|
||||||
|
|
||||||
|
| Construct | Form | Key rules |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| VAR | `[VAR name="..." type="..." unique="..." ] ... [/VAR]` | `name` identifies a context variable. Documented types: `string`, `boolean`, `number`, `date`, `object`, `iterable`. `unique` defaults to `false` and is meaningful only for `iterable`. |
|
||||||
|
| VALUE_OF | `[VALUE_OF varname="..." index="..." /]` | Emits a variable value. `index` identifies the position for an iterable and may name another variable. |
|
||||||
|
| SIZE_OF | `[SIZE_OF varname="..." /]` | Returns iterable size, otherwise `0`. |
|
||||||
|
| IS_EMPTY | `[IS_EMPTY varname="..." /]` | Tests missing, null, blank string, empty iterable, or empty map according to the documented rules. |
|
||||||
|
| IS_NOT_EMPTY | `[IS_NOT_EMPTY varname="..." /]` | Logical negative of `IS_EMPTY`. |
|
||||||
|
| CONTAINS | `[CONTAINS varname="..." value="..." /]` or `[CONTAINS varname="..."] ... [/CONTAINS]` | Supports string or iterable input. Attribute `value` and body are alternatives; `value` has priority when both exist. |
|
||||||
|
|
||||||
|
Do not require `type` merely because current examples commonly include it: some examples omit it. Resolve requiredness from the parser relations or runtime contract before emitting an error.
|
||||||
|
|
||||||
|
## Transformations
|
||||||
|
|
||||||
|
| Construct | Form | Key rules |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| FORMAT | `[FORMAT varname="..." type="..." /]` or `[FORMAT type="..." pattern="..."] ... [/FORMAT]` | `varname` takes precedence over the body. Documented formatting covers number and date families; number modes include currency, integer, double, percent, and generic. |
|
||||||
|
| SPLIT | `[SPLIT regex="..." emptyIfBlank="true|false"] ... [/SPLIT]` | Applies a valid regular expression to a string result. `emptyIfBlank` defaults to `false`. An invalid regex is a runtime parsing error and can be checked statically only for a literal. |
|
||||||
|
| TRIM | `[TRIM varname="..." /]` or `[TRIM] ... [/TRIM]` | Trims a named variable or evaluated body. A found variable takes precedence; otherwise the body is evaluated. |
|
||||||
|
|
||||||
|
## Control structures
|
||||||
|
|
||||||
|
- IF shape: `[IF] [CONDITION] ... [/CONDITION] [THEN] ... [/THEN] { [ELSE IF] ... [/ELSE IF] } [ELSE] ... [/ELSE] [/IF]`.
|
||||||
|
- `CONDITION` must evaluate to boolean. There may be multiple `ELSE IF` blocks and at most one final `ELSE`; both are optional.
|
||||||
|
- WHILE shape: `[WHILE ...] [CONDITION] ... [/CONDITION] [DO] ... [/DO] [/WHILE]`.
|
||||||
|
- The WHILE section documents an optional `threshold` with default and maximum `32766`, but also contains a sentence saying the tag has no attributes. Treat this as an explicit documentation inconsistency.
|
||||||
|
- FOR shape: `[FOR varName="item" iterable="items"] ... [/FOR]`. The loop variable exists during the loop and is removed afterward.
|
||||||
|
- The FOR prose depends on both `varName` and `iterable`, but the generated relationship only shows a generic attribute node. Treat formal requiredness, attribute-name casing, duplicate/unknown attributes, null or non-iterable input, and shadowing as unresolved until confirmed against the runtime grammar.
|
||||||
|
|
||||||
|
## Code blocks
|
||||||
|
|
||||||
|
| Kind | Form | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Assignment | `[% target = expression; ... %]` | Mutates variables; documented statements end with `;`. |
|
||||||
|
| Evaluation | `[% expression %]` | Evaluates and returns a result, often boolean in `CONDITION`. |
|
||||||
|
| Output | `[%= variable %]` | Writes a context variable; a missing variable is a runtime error. |
|
||||||
|
|
||||||
|
The expression language examples use assignment, equality/comparison, arithmetic, boolean operators, strings, numbers, booleans, and `null`. The PDFs do not provide a complete operator-precedence grammar; avoid inventing one without a runtime grammar source.
|
||||||
|
|
||||||
|
## ElixForms TAG payloads
|
||||||
|
|
||||||
|
`SCHEMAID` is positional:
|
||||||
|
|
||||||
|
- position 0: plugin name `SCHEMAID`;
|
||||||
|
- position 1: schema ID;
|
||||||
|
- position 2: column reference;
|
||||||
|
- position 3: `IUQOID`;
|
||||||
|
- position 4: line terminator, with a space selecting the default;
|
||||||
|
- position 5: column separator;
|
||||||
|
- position 6: default value;
|
||||||
|
- position 7: date/time formatter;
|
||||||
|
- position 8: language (documented as currently unused).
|
||||||
|
|
||||||
|
Positions 0 through 4 must be present. Preserve commas for omitted intermediate optional positions.
|
||||||
|
|
||||||
|
`GETVALUEBYTAG` is positional:
|
||||||
|
|
||||||
|
- position 0: plugin name `GETVALUEBYTAG`;
|
||||||
|
- position 1: tag name;
|
||||||
|
- position 2: lookup type: `REQUEST`, `MODULE`, or `USER_PROFILE`;
|
||||||
|
- position 3: `IUQOID`;
|
||||||
|
- position 4: weight/order option;
|
||||||
|
- position 5: column separator;
|
||||||
|
- position 6: format string.
|
||||||
|
|
||||||
|
The weight list in the prose and the final syntax example differ in spelling and membership (`UPDATED_FIST`/`UPDATED_FIRST`, `UPDATE_LAST`/`UPDATED_LAST`, and `CONCAT`). Treat exact validation as ambiguous until confirmed against the TAG runtime.
|
||||||
|
|
||||||
|
## Known implementation delta at skill creation
|
||||||
|
|
||||||
|
The current TypeScript tokenizer/parser does not yet model all documented constructs. Notably absent or incomplete are `LOG`, `FOR`, `IS_EMPTY`, `IS_NOT_EMPTY`, case-insensitive spellings, structural ordering inside IF/WHILE, attribute validation beyond VAR type, and full expression parsing. Re-check source before relying on this list because it is expected to shrink as the linter evolves.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
name: verify-eftl-linter
|
||||||
|
description: Verify the EFTL linter and VS Code language support with repository checks, tokenizer/parser cases, diagnostic range review, TextMate grammar checks, snippets, examples, and LSP behavior. Use after EFTL implementation changes, while diagnosing regressions, when adding tests, or before declaring a linter feature complete.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verify EFTL Linter
|
||||||
|
|
||||||
|
Validate the core language implementation first, then the LSP and editor surfaces affected by the change. Read [references/verification-matrix.md](references/verification-matrix.md) when selecting cases.
|
||||||
|
|
||||||
|
## Run the baseline check
|
||||||
|
|
||||||
|
From the repository root, run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node .agents/skills/verify-eftl-linter/scripts/check-repository.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
The script parses contributed JSON, checks referenced files, compiles TypeScript, runs an existing test script when present, and smoke-tests the compiled tokenizer/parser. Pass `--lint` only when the repository has a working ESLint configuration.
|
||||||
|
|
||||||
|
Treat this script as a floor, not as feature proof.
|
||||||
|
|
||||||
|
## Build a focused test set
|
||||||
|
|
||||||
|
For each changed rule, include:
|
||||||
|
|
||||||
|
- the smallest valid form;
|
||||||
|
- every optional form or bodyless/block alternative;
|
||||||
|
- a representative nested form;
|
||||||
|
- missing, duplicated, invalid, and out-of-order attributes or children;
|
||||||
|
- mismatched, missing, and unexpected closing delimiters;
|
||||||
|
- incomplete input at end of file for live-edit recovery;
|
||||||
|
- lowercase and mixed-case spellings when the runtime is case-insensitive;
|
||||||
|
- LF, CRLF, multiline, and non-ASCII range cases;
|
||||||
|
- a regression case from `examples/` or the reported bug.
|
||||||
|
|
||||||
|
Assert token types and spans separately from parser errors. Assert diagnostic code, severity, message, and exact LSP range; do not rely only on snapshot text.
|
||||||
|
|
||||||
|
## Verify by layer
|
||||||
|
|
||||||
|
1. Run tokenizer/parser tests without VS Code.
|
||||||
|
2. Run server-level tests for config, diagnostic caps, declaration/reference scope, and range conversion.
|
||||||
|
3. Parse all JSON contribution files and inspect TextMate captures for the changed construct.
|
||||||
|
4. Open a representative `.eftl` file in an Extension Development Host when highlighting, folding, snippets, activation, or navigation changed.
|
||||||
|
5. Confirm valid examples produce no diagnostics and each invalid example produces the intended minimal set.
|
||||||
|
|
||||||
|
When no automated harness exists for a required layer, add one if it is stable and proportionate; otherwise document the exact manual check and result.
|
||||||
|
|
||||||
|
## Interpret failures
|
||||||
|
|
||||||
|
- A compilation failure blocks all further verification.
|
||||||
|
- A failure caused by a known baseline issue must be reproduced on the pre-change baseline before being labeled unrelated.
|
||||||
|
- Diagnostic cascades usually indicate parser recovery problems; fix recovery instead of weakening assertions.
|
||||||
|
- TextMate highlighting success does not prove tokenizer/parser support, and parser success does not prove editor integration.
|
||||||
|
- Never declare `npm run lint` successful in the current baseline: ESLint 9 has no flat configuration until the repository adds one.
|
||||||
|
|
||||||
|
Report commands, pass/fail counts, skipped layers, and residual risks.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Verify EFTL Linter"
|
||||||
|
short_description: "Test EFTL diagnostics and editor support"
|
||||||
|
default_prompt: "Use $verify-eftl-linter to validate this EFTL linter change end to end."
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# EFTL verification matrix
|
||||||
|
|
||||||
|
## Core matrix
|
||||||
|
|
||||||
|
| Area | Positive cases | Negative and recovery cases |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Roots | one and multiple EFTL fragments with surrounding user text | close without open, missing close, nested roots if forbidden by confirmed grammar |
|
||||||
|
| Blocks | correctly nested VAR, IF, WHILE, functions, expressions, comments | crossing closes, incomplete close, unexpected close, unfinished or unknown `[` construct; assert termination |
|
||||||
|
| Attributes | documented required and optional attributes in varied order | missing required, duplicate, unknown, unquoted, invalid enum/boolean/number |
|
||||||
|
| IF | condition + then, repeated else-if, optional final else | missing condition/then, else before then, duplicate else, branch after else |
|
||||||
|
| WHILE | condition + do, confirmed threshold boundaries | missing child, reversed order, invalid threshold, incomplete body |
|
||||||
|
| FOR | iterable variable and scoped loop variable | missing attributes, unknown iterable when statically knowable, reference after scope |
|
||||||
|
| Expressions | assignment, evaluation, output, supported literals/operators | unterminated block, malformed operator, missing semicolon where required |
|
||||||
|
| Functions | bodyless and block variants, precedence rules | mutually exclusive inputs, wrong body shape, invalid literal regex |
|
||||||
|
| TAG payload | minimal and full positional forms | missing mandatory position, invalid literal enum, preserved empty positions |
|
||||||
|
| Variables | declaration, reference, shadowing policy, iterable index | invalid type, duplicate declaration policy, undefined reference, invalid index type |
|
||||||
|
|
||||||
|
For every tag keyword, test the exact boundary after its name so a longer unknown name such as `[VARIABLE]` is not classified as `[VAR ...]`.
|
||||||
|
|
||||||
|
Only assert rules confirmed by the PDFs or runtime grammar. Keep ambiguous rules as pending cases rather than opinionated errors.
|
||||||
|
|
||||||
|
## Range matrix
|
||||||
|
|
||||||
|
Exercise each diagnostic at:
|
||||||
|
|
||||||
|
- first character and end of file;
|
||||||
|
- after ASCII and non-ASCII text;
|
||||||
|
- on a later line with LF and CRLF;
|
||||||
|
- inside a multiline token or comment;
|
||||||
|
- after an incremental edit that changes preceding line lengths.
|
||||||
|
|
||||||
|
LSP positions are zero-based UTF-16 line/character pairs. Prefer storing source offsets internally and converting with the current `TextDocument` at publication time.
|
||||||
|
|
||||||
|
## Editor matrix
|
||||||
|
|
||||||
|
When syntax changes, inspect:
|
||||||
|
|
||||||
|
- language activation and file association;
|
||||||
|
- TextMate scope on open name, close name, attributes, strings, and delimiters;
|
||||||
|
- bracket matching and auto-closing;
|
||||||
|
- folding start/end markers;
|
||||||
|
- snippet insertion and tab stops;
|
||||||
|
- go-to-definition selection range;
|
||||||
|
- consistency of examples and README syntax.
|
||||||
|
|
||||||
|
## Release evidence
|
||||||
|
|
||||||
|
Record:
|
||||||
|
|
||||||
|
1. exact commands executed;
|
||||||
|
2. automated pass/fail totals;
|
||||||
|
3. Extension Development Host scenarios checked;
|
||||||
|
4. known baseline failures reproduced independently;
|
||||||
|
5. unverified runtime semantics or ambiguous documentation.
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const includeLint = args.includes('--lint');
|
||||||
|
const positional = args.filter(arg => !arg.startsWith('--'));
|
||||||
|
const root = resolve(positional[0] ?? process.cwd());
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJson(relativePath) {
|
||||||
|
const absolutePath = resolve(root, relativePath);
|
||||||
|
if (!existsSync(absolutePath)) {
|
||||||
|
fail(`Missing ${relativePath}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(readFileSync(absolutePath, 'utf8'));
|
||||||
|
} catch (error) {
|
||||||
|
fail(`Invalid JSON in ${relativePath}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runNpm(script) {
|
||||||
|
const windows = process.platform === 'win32';
|
||||||
|
const command = windows ? (process.env.ComSpec ?? 'cmd.exe') : 'npm';
|
||||||
|
const commandArgs = windows ? ['/d', '/s', '/c', `npm run ${script}`] : ['run', script];
|
||||||
|
const result = spawnSync(command, commandArgs, {
|
||||||
|
cwd: root,
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
if (result.error) {
|
||||||
|
fail(`Could not run npm script ${script}: ${result.error.message}`);
|
||||||
|
}
|
||||||
|
if (result.status !== 0) {
|
||||||
|
fail(`npm run ${script} exited with ${result.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertContributionPaths(packageJson) {
|
||||||
|
const paths = [packageJson.main];
|
||||||
|
for (const language of packageJson.contributes?.languages ?? []) {
|
||||||
|
paths.push(language.configuration);
|
||||||
|
}
|
||||||
|
for (const grammar of packageJson.contributes?.grammars ?? []) {
|
||||||
|
paths.push(grammar.path);
|
||||||
|
}
|
||||||
|
for (const snippet of packageJson.contributes?.snippets ?? []) {
|
||||||
|
paths.push(snippet.path);
|
||||||
|
}
|
||||||
|
for (const relativePath of paths.filter(Boolean)) {
|
||||||
|
if (!existsSync(resolve(root, relativePath))) {
|
||||||
|
fail(`package.json references missing path ${relativePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function smokeTestCore() {
|
||||||
|
const tokenizerPath = resolve(root, 'out/tokenizer.js');
|
||||||
|
const parserPath = resolve(root, 'out/parser.js');
|
||||||
|
if (!existsSync(tokenizerPath) || !existsSync(parserPath)) {
|
||||||
|
fail('Compiled tokenizer/parser output is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { EftlTokenizer } = require(tokenizerPath);
|
||||||
|
const { EftlParser } = require(parserPath);
|
||||||
|
const sample = readFileSync(resolve(root, 'examples/sample.eftl'), 'utf8');
|
||||||
|
const sampleResult = new EftlTokenizer(sample).tokenize();
|
||||||
|
const sampleParse = new EftlParser(sampleResult.tokens).parse();
|
||||||
|
if (sampleResult.errors.length || sampleParse.errors.length) {
|
||||||
|
fail(`sample.eftl is not accepted: ${sampleResult.errors.length} tokenizer and ${sampleParse.errors.length} parser errors`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mismatched = new EftlTokenizer('[EFTL][IF][/EFTL]').tokenize();
|
||||||
|
if (new EftlParser(mismatched.tokens).parse().errors.length === 0) {
|
||||||
|
fail('Parser smoke case did not reject mismatched nesting');
|
||||||
|
}
|
||||||
|
|
||||||
|
const comment = new EftlTokenizer('[EFTL][!-- unfinished').tokenize();
|
||||||
|
if (comment.errors.length === 0) {
|
||||||
|
fail('Tokenizer smoke case did not reject an unterminated comment');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packageJson = readJson('package.json');
|
||||||
|
readJson('language-configuration.json');
|
||||||
|
readJson('syntaxes/eftl.tmLanguage.json');
|
||||||
|
readJson('snippets/eftl.json');
|
||||||
|
|
||||||
|
runNpm('compile');
|
||||||
|
assertContributionPaths(packageJson);
|
||||||
|
smokeTestCore();
|
||||||
|
|
||||||
|
if (packageJson.scripts?.test) {
|
||||||
|
runNpm('test');
|
||||||
|
} else {
|
||||||
|
console.warn('WARN: package.json has no test script; only smoke cases were run.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeLint) {
|
||||||
|
runNpm('lint');
|
||||||
|
} else if (packageJson.scripts?.lint) {
|
||||||
|
console.warn('WARN: lint was skipped; pass --lint after configuring ESLint.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('EFTL repository baseline checks passed.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`FAIL: ${error.message}`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user