When RangeLink grows to support multiple languages (TypeScript, Java, C/C++, Rust, etc.) and multiple IDEs (VSCode, Xcode, InDesign, Neovim), how do we:
- Ensure feature parity across all language implementations?
- Enforce consistency when adding new features?
- Scale development without duplicating effort?
- Maintain quality across all implementations?
- Single Source of Truth: The specification defines behavior, not any implementation
- Contract-Driven: All implementations must pass the same contract tests
- Language-Agnostic Contracts: Test cases are defined in JSON/YAML, executable in any language
- CI Enforcement: Parity is not optional — CI will fail if implementations diverge
rangeLink/
spec/ # The specification hub
schema/
range-link.schema.json # JSON Schema for all data structures
selection.schema.json # Selection types
config.schema.json # Configuration structure
contracts/ # Behavioral contracts (language-agnostic)
build-link/
single-line.json # Test cases for single-line links
multi-line.json
rectangular mode.json
portable.json
parse-link/
all-formats.json # Test cases for parsing
error-handling.json
edge-cases.json
validation/
delimiter-validation.json # Configuration validation tests
reserved-chars.json
docs/
specification.md # Human-readable spec
examples.md
packages/
rangelink-core-ts/ # TypeScript implementation
src/
RangeLinkBuilder.ts # Implements build-link contracts
RangeLinkParser.ts # Implements parse-link contracts
ConfigValidator.ts # Implements validation contracts
tests/
unit/ # Language-specific unit tests
contracts/ # Contract test runner
test-build-link.ts # Runs spec/contracts/build-link/*.json
test-parse-link.ts
test-validation.ts
package.json
rangelink-core-java/
src/
RangeLinkBuilder.java # Same contracts, Java implementation
RangeLinkParser.java
ConfigValidator.java
tests/
unit/
contracts/
ContractTestRunner.java # Runs spec/contracts/**/*.json
pom.xml
rangelink-core-c/
src/
rangelink_builder.c # C implementation
rangelink_parser.c
config_validator.c
tests/
unit/
contracts/
contract_runner.c # Runs spec/contracts/**/*.json
CMakeLists.txt
# ... more core implementations
rangelink-vscode-extension/
package.json # Depends on rangelink-core-ts
rangelink-xcode-extension/ # Depends on rangelink-core-c or Swift wrapper
# ...
tools/
contract-runner/ # Language-agnostic contract test runner
runner.js # Can execute contracts in any language
validate-parity.sh # Checks all implementations pass all contracts
// spec/schema/range-link.schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"RangeLink": {
"type": "object",
"properties": {
"path": { "type": "string" },
"ranges": {
"type": "array",
"items": { "$ref": "#/definitions/Range" }
},
"isPortable": { "type": "boolean" },
"metadata": { "$ref": "#/definitions/DelimiterMetadata" }
},
"required": ["path"]
},
"Range": {
"type": "object",
"properties": {
"startLine": { "type": "integer", "minimum": 1 },
"startColumn": { "type": "integer", "minimum": 1 },
"endLine": { "type": "integer", "minimum": 1 },
"endColumn": { "type": "integer", "minimum": 1 },
"isRectangularMode": { "type": "boolean" }
},
"required": ["startLine", "endLine"]
}
}
}// spec/contracts/build-link/rectangular mode.json
{
"name": "rectangular_mode_selection",
"description": "Multiple selections with same column range should produce double hash",
"testCases": [
{
"name": "simple_column_selection",
"input": {
"path": "src/file.ts",
"selections": [
{ "line": 10, "startChar": 5, "endChar": 10 },
{ "line": 11, "startChar": 5, "endChar": 10 },
{ "line": 12, "startChar": 5, "endChar": 10 }
],
"config": {
"delimiterLine": "L",
"delimiterPosition": "C",
"delimiterHash": "#",
"delimiterRange": "-"
}
},
"expected": {
"link": "src/file.ts##L10C6-L12C11",
"isRectangularMode": true
}
},
{
"name": "rectangular_mode_with_custom_delimiters",
"input": {
"path": "src/file.ts",
"selections": [
{ "line": 5, "startChar": 3, "endChar": 8 },
{ "line": 6, "startChar": 3, "endChar": 8 }
],
"config": {
"delimiterLine": "LINE",
"delimiterPosition": "COL",
"delimiterHash": "##",
"delimiterRange": "TO"
}
},
"expected": {
"link": "src/file.ts####LINE5COL3-TOCOL8LINE6",
"isRectangularMode": true
}
}
]
}// spec/contracts/parse-link/all-formats.json
{
"name": "parse_all_link_formats",
"testCases": [
{
"name": "parse_single_line",
"input": { "link": "src/file.ts#L42" },
"expected": {
"path": "src/file.ts",
"ranges": [{ "startLine": 42, "endLine": 42 }]
}
},
{
"name": "parse_rectangular_mode",
"input": { "link": "src/file.ts##L10C5-L20C10" },
"expected": {
"path": "src/file.ts",
"ranges": [
{
"startLine": 10,
"startColumn": 5,
"endLine": 20,
"endColumn": 10,
"isRectangularMode": true
}
]
}
},
{
"name": "parse_portable_link",
"input": { "link": "src/file.ts#L10C5-L20C10~#~L~-~C~" },
"expected": {
"path": "src/file.ts",
"isPortable": true,
"metadata": {
"hash": "#",
"line": "L",
"range": "-",
"column": "C"
},
"ranges": [
{
"startLine": 10,
"startColumn": 5,
"endLine": 20,
"endColumn": 10
}
]
}
}
]
}// packages/rangelink-core-ts/tests/contracts/test-build-link.ts
import * as fs from 'fs';
import * as path from 'path';
import { RangeLinkBuilder } from '../../src/RangeLinkBuilder';
describe('Contract Tests: Build Link', () => {
const contractDir = path.join(__dirname, '../../../spec/contracts/build-link');
const contractFiles = fs
.readdirSync(contractDir)
.filter((f) => f.endsWith('.json'))
.map((f) => path.join(contractDir, f));
contractFiles.forEach((contractFile) => {
const contract = JSON.parse(fs.readFileSync(contractFile, 'utf-8'));
describe(contract.name, () => {
contract.testCases.forEach((testCase: any) => {
it(testCase.name, () => {
const builder = new RangeLinkBuilder(testCase.input.config);
const result = builder.build(testCase.input.path, testCase.input.selections);
expect(result.link).toBe(testCase.expected.link);
expect(result.isRectangularMode).toBe(testCase.expected.isRectangularMode);
});
});
});
});
});// packages/rangelink-core-java/src/test/java/contracts/ContractTestRunner.java
public class BuildLinkContractTests {
@ParameterizedTest
@JsonSource("../../../../spec/contracts/build-link/*.json")
public void testContracts(Contract contract) {
for (TestCase testCase : contract.getTestCases()) {
RangeLinkBuilder builder = new RangeLinkBuilder(testCase.getInput().getConfig());
Result result = builder.build(
testCase.getInput().getPath(),
testCase.getInput().getSelections()
);
assertEquals(testCase.getExpected().getLink(), result.getLink());
assertEquals(testCase.getExpected().isRectangularMode(), result.isRectangularMode());
}
}
}# .github/workflows/validate-parity.yml
name: Validate Feature Parity
on: [push, pull_request]
jobs:
validate-parity:
runs-on: ubuntu-latest
strategy:
matrix:
implementation:
- rangelink-core-ts
- rangelink-core-java
- rangelink-core-c
steps:
- uses: actions/checkout@v3
- name: Run contract tests
run: |
cd packages/${{ matrix.implementation }}
npm run test:contracts # or mvn test, cmake test, etc.
- name: Verify all contracts passed
run: |
./tools/contract-runner/validate-parity.sh
# Fails if ANY implementation fails ANY contract-
Define in Specification:
// spec/contracts/build-link/multi-range.json { "name": "multi_range_selection", "testCases": [ { "name": "two_ranges", "input": { "path": "src/file.ts", "ranges": [ { "startLine": 10, "endLine": 20 }, { "startLine": 30, "endLine": 40 } ] }, "expected": { "link": "src/file.ts#L10-L20,L30-L40" } } ] }
-
All Implementations Must Pass:
- Update
rangelink-core-ts→ runnpm run test:contracts - Update
rangelink-core-java→ runmvn test:contracts - Update
rangelink-core-c→ runcmake test:contracts - CI fails if any implementation doesn't pass new contracts
- Update
-
Extension Selectively Exposes:
// packages/rangelink-vscode-extension/src/extension.ts if (vscode.window.activeTextEditor.selections.length > 1) { // VSCode supports multiple selections, so expose multi-range const link = builder.buildMultiRange(path, selections); } else { // Single selection const link = builder.build(path, selection); }
- Jest with custom JSON loader for contract tests
- Swagger/OpenAPI for API documentation generation
- ajv for JSON Schema validation
- JUnit 5 with
@ParameterizedTestand custom JSON source - Jackson for JSON parsing
- jsonschema2pojo to generate POJOs from schemas
- Catch2 or Google Test with custom JSON loader
- nlohmann/json for JSON parsing
- valijson for JSON Schema validation
- Docker containers for consistent test environments
- Makefile for cross-language build/test commands
- GitHub Actions for CI/CD across all languages
- Feature Parity Guaranteed: CI enforces it; can't ship without it
- Faster Development: Write spec once, implement in multiple languages
- Better Testing: Shared contracts = comprehensive coverage
- Clear Documentation: Spec serves as living documentation
- Platform Flexibility: Extensions expose what their platform supports
- Scalability: Easy to add new languages/IDEs
Solution: Contracts define WHAT, implementations define HOW. Use language-specific unit tests for implementation details.
Solution: JSON Schema validation in CI; schema changes require all implementations to update.
Solution: Docker containers for consistent environments; Makefile for unified commands.
Solution: Detailed error messages; implementation-specific unit tests for debugging.
This specification-first, contract-driven approach ensures that RangeLink can scale to any language and any IDE while maintaining quality, feature parity, and development velocity. The specification is the single source of truth, and contracts enforce it automatically.