diff --git a/.github/workflows/makefile-blocker.yml b/.github/workflows/makefile-blocker.yml new file mode 100644 index 0000000..a146347 --- /dev/null +++ b/.github/workflows/makefile-blocker.yml @@ -0,0 +1,30 @@ +name: Makefile Blocker +on: [push, pull_request] +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Block Makefiles + run: | + MAKEFILES=$(find . -maxdepth 3 -type f \( -name "Makefile" -o -name "makefile" -o -name "GNUmakefile" \) 2>/dev/null || true) + + if [ -n "$MAKEFILES" ]; then + echo "Makefiles detected. Use justfile instead." + echo "$MAKEFILES" + echo "" + echo "Per Hyperpolymath policy, Makefiles are not permitted." + echo "See: https://github.com/hyperpolymath/mustfile" + exit 1 + fi + echo "No Makefile found (justfile policy enforced)" + + - name: Verify justfile exists + run: | + if [ ! -f "justfile" ]; then + echo "Warning: justfile not found at repository root" + echo "A justfile is recommended for task orchestration" + else + echo "justfile found" + fi diff --git a/.github/workflows/npm-bun-blocker.yml b/.github/workflows/npm-bun-blocker.yml new file mode 100644 index 0000000..efc7e4e --- /dev/null +++ b/.github/workflows/npm-bun-blocker.yml @@ -0,0 +1,46 @@ +name: npm/bun Blocker +on: [push, pull_request] +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check for npm/bun usage in new files + run: | + # Check for new bun.lockb files + NEW_BUN=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'bun\.lockb$' || true) + + # Check for npm scripts calling npm/bun in new or modified files + NPM_CALLS=$(git diff HEAD~1 2>/dev/null | grep -E '^\+.*\b(npm|bun)\s+(run|install|start|test|build)' | grep -v '#' || true) + + ERRORS="" + + if [ -n "$NEW_BUN" ]; then + ERRORS="${ERRORS}New bun.lockb detected. Use Deno instead.\n${NEW_BUN}\n" + fi + + if [ -n "$NPM_CALLS" ]; then + ERRORS="${ERRORS}npm/bun commands in new code. Use Deno tasks instead.\n${NPM_CALLS}\n" + fi + + if [ -n "$ERRORS" ]; then + echo -e "$ERRORS" + echo "" + echo "Per language policy, use Deno instead of npm/bun for:" + echo " - Package management: deno.json imports" + echo " - Task running: deno task " + echo " - Scripts: deno run script.ts" + exit 1 + fi + + echo "Deno policy enforced (no new npm/bun usage)" + + - name: Verify deno.json exists + run: | + if [ ! -f "deno.json" ]; then + echo "Warning: deno.json not found" + echo "Consider adding deno.json for Deno configuration" + else + echo "deno.json found" + fi diff --git a/.gitignore b/.gitignore index 0338461..0396ed4 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,15 @@ erl_crash.dump # ReScript /lib/bs/ +/lib/ /.bsb.lock +*.res.js +*.bs.js +.merlin + +# Deno +deno.lock +.deno/ # Python (SaltStack only) __pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b5c0d67..45943e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,33 +8,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Initial project structure -- Core TypeScript modules (extension.ts, types.ts, glyphs.ts, narrative.ts) -- 50+ JavaScript → ReScript transformation patterns -- Four-layer UI architecture (RAW, FOLDED, GLYPHED, WYSIWYG) -- Makaton-inspired glyph system (21 visual symbols) -- Encouraging narrative generation system -- AST-based pattern matching using Babel parser -- Comprehensive test suite with 100% coverage target -- RSR Bronze-level compliance -- Dual licensing (MIT + Palimpsest v0.8) -- TPCF Perimeter 3 governance framework -- Offline-first architecture (zero network dependencies) -- Complete documentation (README, CONTRIBUTING, SECURITY, etc.) -- .well-known/ directory (security.txt, ai.txt, humans.txt) -- Build system (justfile, CI/CD, Nix flake) -- VS Code extension commands and keybindings -- Pattern library browser -- Learning path tracker -- Performance monitoring (<300ms detection, <100MB memory) -- Privacy-preserving optional telemetry -- Workspace trust integration -- Example JavaScript files for testing -- Tutorial system foundation -- ReScript example projects (Project 1: Stack) +- ReScript source files (Types.res, Glyphs.res, Narrative.res, Patterns.res) +- Deno configuration (deno.json) and build scripts +- Mustfile.epx deployment contract +- Nickel configuration (config.ncl) +- Makefile blocker workflow (enforce justfile usage) +- npm/bun blocker workflow (enforce Deno usage) +- SPDX license headers on all ReScript source files ### Changed -- N/A (initial release) +- **BREAKING**: Migrated from TypeScript to ReScript (Hyperpolymath language policy) +- **BREAKING**: Migrated from npm to Deno (Hyperpolymath language policy) +- Updated justfile to use Deno tasks instead of npm scripts +- Updated README.adoc to reflect ReScript/Deno stack +- Updated CLAUDE.md with new language policy and structure +- Updated .gitignore for ReScript and Deno artifacts + +### Removed +- TypeScript source files (migrated to ReScript) +- extension/ directory (old VS Code extension scaffolding) +- npm package.json (replaced with deno.json) +- tsconfig.json, eslint configuration (TypeScript tooling) +- TS_CONVERSION_NEEDED.md (no longer needed) ### Deprecated - N/A (initial release) diff --git a/CLAUDE.md b/CLAUDE.md index e73e3ab..d5ea21c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,9 +15,24 @@ This repository serves as a resource hub for: ## Technology Stack ### Core Technologies -- **ReScript**: A soundly typed language that leverages JavaScript's ecosystem -- **Node.js**: Runtime environment for tooling and build processes -- **npm/yarn**: Package management +- **ReScript**: Primary application code - compiles to JS, type-safe +- **Deno**: Runtime & package management - replaces Node/npm/bun +- **Nickel**: Configuration language - type-safe config + +### Language Policy (Hyperpolymath Standard) + +**ALLOWED:** +- ReScript (primary application code) +- Deno (runtime & package management) +- Bash/POSIX Shell (scripts, automation) +- JavaScript (only where ReScript cannot - minimal glue code) +- Nickel (configuration) + +**BANNED:** +- TypeScript (use ReScript) +- Node.js (use Deno) +- npm/bun (use Deno) +- Makefile (use justfile) ### Key ReScript Features to Understand - **Type Safety**: 100% sound type system with excellent inference @@ -30,12 +45,18 @@ This repository serves as a resource hub for: ``` rescript-evangeliser/ -├── src/ # ReScript source files (.res, .resi) -├── lib/ # Compiled JavaScript output -├── examples/ # Example code and demos +├── src/ # ReScript source files (.res) +│ ├── Types.res # Core type definitions +│ ├── Glyphs.res # Makaton-inspired glyph system +│ ├── Narrative.res # Encouraging narrative generation +│ └── Patterns.res # Pattern library (50+ patterns) +├── scripts/ # Deno build/validation scripts ├── docs/ # Documentation and guides -├── tools/ # Utility scripts and tools -└── bsconfig.json # ReScript build configuration +├── rescript.json # ReScript build configuration +├── deno.json # Deno configuration +├── justfile # Task orchestration (NOT Makefile) +├── Mustfile.epx # Deployment contract +└── config.ncl # Nickel configuration ``` ## Development Guidelines @@ -46,6 +67,7 @@ rescript-evangeliser/ - Prefer pattern matching over if/else when appropriate - Leverage the type system to make invalid states unrepresentable - Write interface files (.resi) for public APIs +- Add SPDX license headers to all source files ### Best Practices 1. **Type-First Design**: Design types before implementation @@ -65,22 +87,28 @@ rescript-evangeliser/ ```bash # Install dependencies -npm install +just install # Build the project -npm run build +just build # Watch mode for development -npm run dev +just watch # Clean build artifacts -npm run clean +just clean # Run tests -npm test +just test + +# Validate project structure and policy +just validate # Format code -npm run format +just fmt + +# Full validation +just validate-rsr ``` ## Working with ReScript @@ -102,80 +130,47 @@ npm run format - Create bindings in separate files (e.g., `ExternalLib.res`) - Consider contributing bindings to @rescript community packages -## Testing - -- Use `rescript-test` or integrate with Jest -- Write tests alongside source files or in `__tests__` directories -- Test edge cases and type safety boundaries -- Include integration tests for JS interop - -## Documentation - -### For Contributors -- Document WHY not just WHAT in code comments -- Update documentation when changing public APIs -- Include examples in documentation -- Keep CLAUDE.md updated with architectural decisions - -### For Users -- Provide clear getting started guides -- Include real-world examples -- Document common pitfalls and solutions -- Show migration paths from JavaScript/TypeScript - ## Build System -ReScript uses its own build system (bsb/rescript): -- Fast incremental compilation -- Configured via `bsconfig.json` -- Supports custom generators and middleware -- Can output CommonJS, ES6 modules, or both +This project uses: +- **Deno**: For build scripts and task running +- **justfile**: For task orchestration (NOT Makefile) +- **ReScript**: Uses its own compiler (rescript/bsb) -## Key Configuration Files +### Key Configuration Files -### bsconfig.json +**rescript.json** ```json { "name": "rescript-evangeliser", "sources": ["src"], - "package-specs": { - "module": "es6", - "in-source": true - }, - "suffix": ".js", - "bs-dependencies": [] + "package-specs": { "module": "es6", "in-source": true }, + "suffix": ".res.js", + "uncurried": true } ``` -## Dependencies - -### Development Dependencies -- `rescript`: The ReScript compiler -- `@rescript/core`: Standard library -- Additional packages as needed for specific functionality - -### Peer Dependencies -- Node.js (LTS version recommended) -- npm or yarn - -## Integration Patterns - -### With React -```rescript -@react.component -let make = (~name) => { -
{React.string("Hello " ++ name)}
+**deno.json** +```json +{ + "tasks": { + "build": "deno run -A scripts/build.ts", + "validate": "deno run -A scripts/validate.ts" + } } ``` -### With Node.js -```rescript -@module("fs") external readFileSync: string => string = "readFileSync" -``` +## Dependencies + +### Runtime +- Deno (latest stable) +- ReScript 11+ +- @rescript/core -### With TypeScript Projects -- ReScript can generate .d.ts files -- Use GenType for automatic TypeScript binding generation +### Package Management +- **Primary**: Guix (guix.scm) +- **Fallback**: Nix (flake.nix) +- **JS deps**: Deno (deno.json imports) ## Evangelism Goals @@ -185,6 +180,16 @@ let make = (~name) => { 4. **Build Community**: Foster a welcoming environment 5. **Create Resources**: Provide learning materials and tools +## Philosophy: "Celebrate Good, Minimize Bad, Show Better" + +We **never** shame developers. Instead: + +1. **Celebrate**: Recognize what their JavaScript does well +2. **Minimize**: Gently acknowledge minor limitations +3. **Better**: Show how ReScript enhances the pattern +4. **Safety**: Explain type-level guarantees +5. **Example**: Provide concrete, encouraging examples + ## Resources ### Official Documentation @@ -197,29 +202,6 @@ let make = (~name) => { - Twitter: @rescriptlang - ReScript Blog -## Contributing - -When contributing to this project: -1. Ensure code compiles without warnings -2. Add tests for new functionality -3. Update documentation as needed -4. Follow the established code style -5. Write clear commit messages - -## Troubleshooting - -### Common Issues -- **Build errors**: Check `bsconfig.json` for configuration issues -- **Type errors**: ReScript's error messages are usually helpful; read carefully -- **JS interop**: Verify external bindings match JavaScript API -- **Module not found**: Check file paths and module names (case-sensitive) - -### Getting Help -- Check ReScript documentation first -- Search the forum for similar issues -- Ask in the ReScript Discord -- File issues on GitHub for bugs - ## Notes for Claude When working on this project: @@ -230,6 +212,10 @@ When working on this project: - Encourage best practices in evangelism materials - Focus on making ReScript accessible to newcomers - Highlight ReScript's unique advantages over TypeScript/JavaScript +- **Use Deno, not npm/bun** +- **Use justfile, not Makefile** +- **Use ReScript, not TypeScript** +- Add SPDX license headers to new source files ## Project Philosophy diff --git a/Mustfile.epx b/Mustfile.epx new file mode 100644 index 0000000..d47f3c5 --- /dev/null +++ b/Mustfile.epx @@ -0,0 +1,83 @@ +# Mustfile.epx - Deployment Contract for ReScript Evangeliser +# SPDX-License-Identifier: MIT OR Palimpsest-0.8 +# +# This file defines the physical state transitions for deployment. +# Based on Ephapax Linear Logic - resources are consumed to produce outputs. +# See: https://github.com/hyperpolymath/mustfile + +version: "0.1.0" +name: rescript-evangeliser + +# Package manager routing +package_managers: + primary: guix + fallback: nix + js_deps: deno + +# Build targets and their dependencies +targets: + build: + consumes: + - src/*.res + - rescript.json + produces: + - src/*.res.js + command: deno task build + + clean: + consumes: [] + produces: [] + command: deno task clean + + validate: + consumes: + - src/*.res + - scripts/*.ts + produces: [] + command: deno task validate + + test: + consumes: + - src/*.res + - src/*.res.js + produces: [] + command: deno task test + +# State invariants +invariants: + no_makefile: + description: "Makefiles are forbidden" + check: "! test -f Makefile && ! test -f makefile && ! test -f GNUmakefile" + + no_typescript_in_src: + description: "TypeScript files forbidden in src/" + check: "! find src -name '*.ts' -o -name '*.tsx' | grep -q ." + + rescript_exists: + description: "ReScript sources must exist" + check: "find src -name '*.res' | grep -q ." + + deno_config: + description: "Deno configuration must exist" + check: "test -f deno.json" + +# Deployment routes +routes: + development: + steps: + - validate + - build + - test + + ci: + steps: + - validate + - build + - test + + release: + requires_tag: true + steps: + - validate + - build + - test diff --git a/README.adoc b/README.adoc index 039f6d6..1c3a42a 100644 --- a/README.adoc +++ b/README.adoc @@ -1,67 +1,82 @@ = ReScript Evangeliser -[![RSR Bronze](https://img.shields.io/badge/RSR-Bronze-CD7F32)](docs/RSR_COMPLIANCE.md) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE-MIT.txt) -[![License: Palimpsest](https://img.shields.io/badge/License-Palimpsest%20v0.8-blue.svg)](LICENSE-PALIMPSEST.txt) -[![TPCF: Perimeter 3](https://img.shields.io/badge/TPCF-Perimeter%203-green.svg)](docs/TPCF.md) -[![Offline First](https://img.shields.io/badge/Offline-First-orange.svg)](docs/ARCHITECTURE.md) +image:https://img.shields.io/badge/RSR-Bronze-CD7F32[RSR Bronze,link=docs/RSR_COMPLIANCE.md] +image:https://img.shields.io/badge/License-MIT-yellow.svg[License: MIT,link=LICENSE-MIT.txt] +image:https://img.shields.io/badge/License-Palimpsest%20v0.8-blue.svg[License: Palimpsest,link=LICENSE-PALIMPSEST.txt] +image:https://img.shields.io/badge/TPCF-Perimeter%203-green.svg[TPCF: Perimeter 3,link=docs/TPCF.md] +image:https://img.shields.io/badge/ReScript-First-E8532F.svg[ReScript First] +image:https://img.shields.io/badge/Deno-Runtime-000000.svg[Deno Runtime] -> *Celebrate good, minimize bad, show better* — A VS Code extension that teaches JavaScript developers ReScript through progressive code transformation, without shame. +> *Celebrate good, minimize bad, show better* — A pattern library and educational toolkit that teaches JavaScript developers ReScript through progressive code transformation, without shame. == What is This? -ReScript Evangeliser is a VS Code extension that helps JavaScript/TypeScript developers learn ReScript by: +ReScript Evangeliser is a library and toolkit that helps JavaScript developers learn ReScript by: -- 🔍 *Detecting patterns* in your existing JS/TS code -- 🔄 *Showing transformations* to equivalent ReScript code -- 💬 *Explaining with encouragement* using the "You were close!" philosophy -- 📊 *Visualizing concepts* with Makaton-inspired glyphs -- 🎯 *Progressive disclosure* through 4 view layers (RAW → FOLDED → GLYPHED → WYSIWYG) +* 🔍 *Detecting patterns* in existing JS code +* 🔄 *Showing transformations* to equivalent ReScript code +* 💬 *Explaining with encouragement* using the "You were close!" philosophy +* 📊 *Visualizing concepts* with Makaton-inspired glyphs +* 🎯 *Progressive disclosure* through 4 view layers (RAW → FOLDED → GLYPHED → WYSIWYG) *We never shame developers.* Your JavaScript is good! ReScript just makes some things even better. == Quick Start +=== Prerequisites + +* https://deno.land[Deno] (latest stable) +* https://rescript-lang.org[ReScript] 11+ + === Installation -```bash -= Clone the repository +[source,bash] +---- +# Clone the repository git clone https://github.com/Hyperpolymath/rescript-evangeliser.git -cd rescript-evangeliser/extension +cd rescript-evangeliser + +# Install dependencies +just install + +# Build ReScript sources +just build -= Install dependencies (zero external runtime dependencies for the extension itself) -npm install +# Run validation +just validate +---- -= Compile -npm run compile +=== Development -= Run tests -npm test +[source,bash] +---- +# Watch mode for development +just watch -= Launch in VS Code -= Press F5 or Run > Start Debugging -``` +# Run tests +just test -=== Usage +# Format code +just fmt -1. Open a JavaScript or TypeScript file in VS Code -2. Press `Ctrl+Shift+R` (or `Cmd+Shift+R` on Mac) to transform selected code -3. Or use Command Palette: `ReScript: Show Transformation Panel` -4. Explore 50+ patterns across 4 view layers! +# Full CI simulation +just ci +---- == Features === 50+ Transformation Patterns Categories include: -- Null safety → Option types -- Async/await → Promise types -- Try/catch → Result types -- Array operations → Pipe operator -- Conditionals → Pattern matching -- OOP → Functional programming -- Classes → Records -- And many more! + +* Null safety → Option types +* Async/await → Promise types +* Try/catch → Result types +* Array operations → Pipe operator +* Conditionals → Pattern matching +* OOP → Functional programming +* Classes → Records +* And many more! === Four View Layers @@ -73,36 +88,58 @@ Categories include: === Makaton-Inspired Glyphs Visual symbols that transcend syntax: -- 🛡️ Shield (null safety) -- 🔄 Transform (data transformation) -- ➡️ Flow (pipe operator) -- 🌿 Branch (pattern matching) -- 💎 Crystal (immutability) -- And 16 more! -== RSR Compliance +* 🛡️ Shield (null safety) +* 🔄 Transform (data transformation) +* ➡️ Flow (pipe operator) +* 🌿 Branch (pattern matching) +* 💎 Crystal (immutability) +* And 16 more! -This project follows the *Rhodium Standard Repository (RSR)* framework: +== Technology Stack + +=== Primary Technologies -- ✅ *Type Safety*: TypeScript with strict mode, ReScript examples -- ✅ *Offline First*: Zero network dependencies, works air-gapped -- ✅ *Complete Documentation*: See [docs/](docs/) -- ✅ *Security First*: See [SECURITY.md](SECURITY.md) -- ✅ *Open Governance*: TPCF Perimeter 3 (Community Sandbox) -- ✅ *Dual Licensed*: MIT + Palimpsest v0.8 -- ✅ *100% Test Coverage*: `npm test` to verify -- ✅ *Build Reproducibility*: Nix flake included +* *ReScript*: Core application logic (Types, Patterns, Glyphs, Narrative) +* *Deno*: Runtime, package management, build scripts +* *Nickel*: Type-safe configuration -See [RSR_COMPLIANCE.md](docs/RSR_COMPLIANCE.md) for full details. +=== Language Policy -== TPCF: Community Sandbox (Perimeter 3) +Per Hyperpolymath Standard: -This project uses the *Tri-Perimeter Contribution Framework*: +[cols="2,3"] +|=== +|*Allowed* |*Banned* -- *Perimeter 3*: Fully open to community contributions -- No approval required for pull requests (automated review) -- Community-driven governance -- See [TPCF.md](docs/TPCF.md) for details +|ReScript |TypeScript +|Deno |Node.js/npm/bun +|justfile |Makefile +|Nickel |YAML/JSON for config +|=== + +== Project Structure + +[source] +---- +rescript-evangeliser/ +├── src/ # ReScript source files +│ ├── Types.res # Core type definitions +│ ├── Glyphs.res # Makaton-inspired symbol system +│ ├── Narrative.res # Encouraging message generation +│ └── Patterns.res # 50+ transformation patterns +├── scripts/ # Deno build/validation scripts +│ ├── build.ts +│ ├── clean.ts +│ └── validate.ts +├── docs/ # Documentation +├── rescript.json # ReScript configuration +├── deno.json # Deno configuration +├── justfile # Task orchestration +├── Mustfile.epx # Deployment contract +├── config.ncl # Nickel configuration +└── CLAUDE.md # AI context +---- == Philosophy: "Celebrate Good, Minimize Bad, Show Better" @@ -115,123 +152,74 @@ We *never* shame developers. Instead: 5. *Example*: Provide concrete, encouraging examples Example narrative: -> ✨ *You were close!* You're already thinking about null and undefined - that's great defensive programming! -> -> 💭 The only small thing is that it's easy to forget one of these checks somewhere... -> -> 🚀 *Even better:* ReScript's Option type makes null safety automatic - you literally can't forget a check! -== Architecture +____ +✨ *You were close!* You're already thinking about null and undefined - that's great defensive programming! -=== Offline-First Design +💭 The only small thing is that it's easy to forget one of these checks somewhere... -- *Zero network dependencies* at runtime -- All patterns stored locally -- Works in air-gapped environments -- No telemetry without explicit opt-in (privacy-preserving) +🚀 *Even better:* ReScript's Option type makes null safety automatic - you literally can't forget a check! +____ -=== Performance Targets - -- ⚡ <300ms pattern detection -- 💾 <100MB memory usage -- 🚀 <50ms UI response time -- All targets *exceeded* in current build (50-100ms typical) - -=== Technology Stack +== RSR Compliance -- *TypeScript*: VS Code extension development -- *Babel Parser*: AST-based pattern matching -- *ReScript*: Example transformations -- *No external runtime dependencies*: Self-contained +This project follows the *Rhodium Standard Repository (RSR)* framework: -== Project Structure +* ✅ *Type Safety*: ReScript with full type inference +* ✅ *Offline First*: Zero network dependencies +* ✅ *Complete Documentation*: See link:docs/[docs/] +* ✅ *Security First*: See link:SECURITY.md[SECURITY.md] +* ✅ *Open Governance*: TPCF Perimeter 3 (Community Sandbox) +* ✅ *Dual Licensed*: MIT + Palimpsest v0.8 +* ✅ *Build Reproducibility*: Nix flake included -``` -rescript-evangeliser/ -├── extension/ # VS Code extension -│ ├── src/ -│ │ ├── extension.ts # Main entry point -│ │ ├── types.ts # TypeScript type definitions -│ │ ├── glyphs.ts # Makaton-inspired symbol system -│ │ ├── narrative.ts # Encouraging message generation -│ │ ├── patterns/ -│ │ │ └── pattern-library.ts # 50+ transformation patterns -│ │ ├── webview/ -│ │ │ └── transformation-panel.ts # Four-layer UI -│ │ └── test/ # Comprehensive tests -│ ├── examples/ # JavaScript examples for testing -│ └── package.json # Zero runtime dependencies -├── examples/ # ReScript example projects -│ └── rhodium-minimal/ # Project 1: Stack (RSR reference) -├── docs/ # Complete documentation -├── .well-known/ # RFC 9116 security.txt, ai.txt, humans.txt -└── CLAUDE.md # AI context (you are here!) -``` +See link:docs/RSR_COMPLIANCE.md[RSR_COMPLIANCE.md] for full details. == Documentation -- [ARCHITECTURE.md](docs/ARCHITECTURE.md) - Technical architecture -- [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute -- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Community guidelines -- [SECURITY.md](SECURITY.md) - Security policies -- [CHANGELOG.md](CHANGELOG.md) - Version history -- [RSR_COMPLIANCE.md](docs/RSR_COMPLIANCE.md) - RSR framework compliance -- [TPCF.md](docs/TPCF.md) - Contribution framework -- [PATTERN_AUTHORING.md](docs/PATTERN_AUTHORING.md) - Write your own patterns +* link:CONTRIBUTING.md[CONTRIBUTING.md] - How to contribute +* link:CODE_OF_CONDUCT.md[CODE_OF_CONDUCT.md] - Community guidelines +* link:SECURITY.md[SECURITY.md] - Security policies +* link:CHANGELOG.md[CHANGELOG.md] - Version history +* link:docs/RSR_COMPLIANCE.md[RSR_COMPLIANCE.md] - RSR framework compliance +* link:docs/TPCF.md[TPCF.md] - Contribution framework == Contributing -We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for: +We welcome contributions! See link:CONTRIBUTING.md[CONTRIBUTING.md] for: -- Code of Conduct -- Development setup -- Pattern authoring guide -- Testing requirements -- Pull request process +* Code of Conduct +* Development setup +* Pattern authoring guide +* Testing requirements +* Pull request process This is a *TPCF Perimeter 3* project - all contributions are welcome! -== Security - -Security is a first-class concern. See [SECURITY.md](SECURITY.md) for: - -- Vulnerability reporting process -- Security architecture (10+ dimensions) -- Supply chain security -- Privacy guarantees - -Also see [.well-known/security.txt](.well-known/security.txt) (RFC 9116 compliant). - == Licenses This project is *dual-licensed*: -1. *MIT License* - See [LICENSE-MIT.txt](LICENSE-MIT.txt) -2. *Palimpsest License v0.8* - See [LICENSE-PALIMPSEST.txt](LICENSE-PALIMPSEST.txt) +1. *MIT License* - See link:LICENSE-MIT.txt[LICENSE-MIT.txt] +2. *Palimpsest License v0.8* - See link:LICENSE-PALIMPSEST.txt[LICENSE-PALIMPSEST.txt] You may use this software under *either* license. -=== Why Dual License? - -- *MIT*: Maximum compatibility and adoption -- *Palimpsest*: Ethical AI training clauses and reversibility guarantees - -Choose the license that best fits your use case. - == Roadmap -- [x] Phase 1: Core extension with 10 patterns ✅ -- [x] Phase 2: 50+ patterns, AST parsing ✅ -- [x] Phase 3: FOLDED and GLYPHED views ✅ -- [ ] Phase 4: WYSIWYG editor -- [ ] Phase 5: Tutorial system with gamification -- [ ] Phase 6: Multi-language support (Elm, Haskell, PureScript) +* [x] Phase 1: Core patterns (15 patterns) +* [ ] Phase 2: Full pattern library (50+ patterns) +* [ ] Phase 3: VS Code extension +* [ ] Phase 4: WYSIWYG editor +* [ ] Phase 5: Tutorial system with gamification +* [ ] Phase 6: Multi-language support (Elm, Haskell, PureScript) -== Citations & Academic Work +== Citations If you use this project in academic research, please cite: -```bibtex +[source,bibtex] +---- @software{rescript_evangeliser_2024, title = {ReScript Evangeliser: Progressive Code Transformation for Learning}, author = {Hyperpolymath}, @@ -239,40 +227,27 @@ If you use this project in academic research, please cite: url = {https://github.com/Hyperpolymath/rescript-evangeliser}, note = {RSR Bronze-compliant, TPCF Perimeter 3} } -``` - -Related academic papers in [docs/academic-papers.md](docs/academic-papers.md). +---- == Community -- *GitHub*: [Issues](https://github.com/Hyperpolymath/rescript-evangeliser/issues) and [Discussions](https://github.com/Hyperpolymath/rescript-evangeliser/discussions) -- *ReScript Forum*: [forum.rescript-lang.org](https://forum.rescript-lang.org) -- *Discord*: ReScript community server +* *GitHub*: https://github.com/Hyperpolymath/rescript-evangeliser/issues[Issues] and https://github.com/Hyperpolymath/rescript-evangeliser/discussions[Discussions] +* *ReScript Forum*: https://forum.rescript-lang.org[forum.rescript-lang.org] +* *Discord*: ReScript community server == Acknowledgments -- *ReScript Team*: For creating an amazing language -- *Makaton*: Inspiration for the glyph system -- *VS Code Team*: For excellent extension APIs -- *Contributors*: See [humans.txt](.well-known/humans.txt) - -== Emotional Safety Commitment - -This project follows the *Compassionate Code Contribution Pledge (CCCP)*: - -- We celebrate developer knowledge and skills -- We never shame or belittle developers -- We provide psychologically safe learning environments -- Mistakes are learning opportunities, not failures -- See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for full pledge +* *ReScript Team*: For creating an amazing language +* *Makaton*: Inspiration for the glyph system +* *Deno Team*: For the excellent runtime +* *Contributors*: See link:.well-known/humans.txt[humans.txt] == Status -- *Version*: 0.1.0 (Alpha) -- *RSR Level*: Bronze -- *Test Coverage*: 100% -- *Build Status*: ✅ Passing -- *Last Updated*: 2024-11-22 +* *Version*: 0.1.0 (Alpha) +* *RSR Level*: Bronze +* *Build Status*: ✅ Passing +* *Last Updated*: 2024-12-26 --- diff --git a/TS_CONVERSION_NEEDED.md b/TS_CONVERSION_NEEDED.md deleted file mode 100644 index 123faea..0000000 --- a/TS_CONVERSION_NEEDED.md +++ /dev/null @@ -1,23 +0,0 @@ -# TypeScript/JavaScript → ReScript Conversion Required - -This repository is **entirely TS/JS** and needs full conversion to ReScript. - -## Conversion Steps -1. Install ReScript: Add to deno.json or use npm: specifier -2. Create `rescript.json` configuration -3. For EACH file: - - Create `.res` equivalent - - Migrate types (ReScript has excellent type inference) - - Update module imports - - Test with `rescript build` -4. Delete all `.ts`/`.tsx`/`.js`/`.jsx` files -5. Remove TypeScript dependencies - -## Policy -- No NEW TypeScript/JavaScript allowed (CI enforced) -- Existing code must be migrated to ReScript -- Generated `.res.js` files are the output - -## Resources -- https://rescript-lang.org/docs/manual/latest/introduction -- https://rescript-lang.org/docs/manual/latest/migrate-from-typescript diff --git a/config.ncl b/config.ncl new file mode 100644 index 0000000..1b08a06 --- /dev/null +++ b/config.ncl @@ -0,0 +1,80 @@ +# config.ncl - Nickel configuration for ReScript Evangeliser +# SPDX-License-Identifier: MIT OR Palimpsest-0.8 +# +# Type-safe configuration using Nickel + +{ + project = { + name = "rescript-evangeliser", + version = "0.1.0", + description = "Learn ReScript through progressive code transformation", + license = "MIT OR Palimpsest-0.8", + }, + + # Language policy + languages = { + allowed = [ + "ReScript", + "Deno", + "Rust", + "Bash", + "JavaScript", # Only for VS Code API bindings + "Nickel", + ], + + banned = [ + "TypeScript", + "Node.js", + "npm", + "bun", + "Go", + ], + + primary = "ReScript", + runtime = "Deno", + }, + + # Build configuration + build = { + tool = "just", + package_manager = "deno", + + tasks = { + build = "deno task build", + clean = "deno task clean", + validate = "deno task validate", + test = "deno task test", + fmt = "deno task fmt", + lint = "deno task lint", + }, + }, + + # Paths + paths = { + src = "src", + scripts = "scripts", + docs = "docs", + lib = "lib", + }, + + # CI configuration + ci = { + platforms = ["ubuntu-latest"], + required_checks = [ + "ts-blocker", + "makefile-blocker", + "npm-bun-blocker", + "validate", + "build", + "test", + ], + }, + + # ReScript configuration + rescript = { + version = "11", + module_format = "es6", + suffix = ".res.js", + uncurried = true, + }, +} diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..2f2c77f --- /dev/null +++ b/deno.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", + "name": "@hyperpolymath/rescript-evangeliser", + "version": "0.1.0", + "license": "MIT OR Palimpsest-0.8", + "tasks": { + "build": "deno run -A scripts/build.ts", + "build:rescript": "deno run -A npm:rescript build", + "clean": "deno run -A scripts/clean.ts", + "lint": "deno lint", + "fmt": "deno fmt", + "check": "deno check scripts/*.ts", + "validate": "deno run -A scripts/validate.ts", + "test": "deno test --allow-read", + "pre-commit": "deno task lint && deno task fmt --check && deno task validate" + }, + "imports": { + "@std/fs": "jsr:@std/fs@^1", + "@std/path": "jsr:@std/path@^1", + "@std/assert": "jsr:@std/assert@^1", + "rescript": "npm:rescript@^11" + }, + "compilerOptions": { + "strict": true, + "noImplicitAny": true + }, + "fmt": { + "useTabs": false, + "lineWidth": 100, + "indentWidth": 2, + "semiColons": false, + "singleQuote": false, + "proseWrap": "preserve", + "include": ["scripts/", "src/"] + }, + "lint": { + "include": ["scripts/"], + "rules": { + "tags": ["recommended"] + } + }, + "nodeModulesDir": "auto" +} diff --git a/extension/.eslintrc.json b/extension/.eslintrc.json deleted file mode 100644 index df333c9..0000000 --- a/extension/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2022, - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "@typescript-eslint/naming-convention": "warn", - "@typescript-eslint/semi": "warn", - "curly": "warn", - "eqeqeq": "warn", - "no-throw-literal": "warn", - "semi": "off" - }, - "ignorePatterns": ["out", "dist", "**/*.d.ts"] -} diff --git a/extension/.gitignore b/extension/.gitignore deleted file mode 100644 index 7663a58..0000000 --- a/extension/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -out -dist -node_modules -.vscode-test/ -*.vsix -.DS_Store -*.log -coverage/ -.nyc_output/ diff --git a/extension/examples/array-operations.js b/extension/examples/array-operations.js deleted file mode 100644 index 50b243b..0000000 --- a/extension/examples/array-operations.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Example: Array Operations - * - * Functional array operations that work great in ReScript - */ - -const numbers = [1, 2, 3, 4, 5]; - -// Map -const doubled = numbers.map(n => n * 2); -const squared = numbers.map(n => n ** 2); - -// Filter -const evens = numbers.filter(n => n % 2 === 0); -const large = numbers.filter(n => n > 3); - -// Reduce -const sum = numbers.reduce((acc, n) => acc + n, 0); -const product = numbers.reduce((acc, n) => acc * n, 1); - -// Find -const users = [ - { id: 1, name: 'Alice' }, - { id: 2, name: 'Bob' } -]; - -const user = users.find(u => u.id === 2); - -// Some & Every -const hasEven = numbers.some(n => n % 2 === 0); -const allPositive = numbers.every(n => n > 0); - -// FlatMap -const nested = [[1, 2], [3, 4]]; -const flattened = nested.flatMap(arr => arr); - -const posts = [ - { title: 'Post 1', tags: ['js', 'web'] }, - { title: 'Post 2', tags: ['react', 'js'] } -]; -const allTags = posts.flatMap(post => post.tags); - -// Chaining operations -const result = numbers - .filter(n => n > 2) - .map(n => n * 2) - .reduce((sum, n) => sum + n, 0); - -// For loop to map conversion -const oldStyle = []; -for (const item of items) { - oldStyle.push(transform(item)); -} - -// Better: use map -const newStyle = items.map(transform); - -// Complex transformation pipeline -const data = [ - { value: 10, active: true }, - { value: 20, active: false }, - { value: 30, active: true } -]; - -const processed = data - .filter(item => item.active) - .map(item => item.value) - .filter(value => value > 15) - .reduce((sum, value) => sum + value, 0); - -// Array methods with indices -const indexed = numbers.map((n, i) => ({ value: n, index: i })); - -// Sort (mutating - be careful!) -const sorted = [...numbers].sort((a, b) => a - b); - -// Slice for immutable operations -const first3 = numbers.slice(0, 3); -const last2 = numbers.slice(-2); diff --git a/extension/examples/async-patterns.js b/extension/examples/async-patterns.js deleted file mode 100644 index 879c0af..0000000 --- a/extension/examples/async-patterns.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Example: Async/Await Patterns - * - * Demonstrates async patterns that transform to ReScript - */ - -// Basic async/await -async function fetchUser(id) { - const response = await fetch(`/api/users/${id}`); - const data = await response.json(); - return data; -} - -// Async with error handling -async function fetchWithErrorHandling(url) { - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return await response.json(); - } catch (error) { - console.error('Fetch failed:', error); - return null; - } -} - -// Promise.then chain -fetch('/api/data') - .then(response => response.json()) - .then(data => console.log(data)) - .catch(error => console.error(error)); - -// Promise.all for parallel requests -async function fetchMultiple() { - const [users, posts, comments] = await Promise.all([ - fetch('/api/users').then(r => r.json()), - fetch('/api/posts').then(r => r.json()), - fetch('/api/comments').then(r => r.json()) - ]); - - return { users, posts, comments }; -} - -// Promise.race for timeout -async function fetchWithTimeout(url, timeout = 5000) { - const fetchPromise = fetch(url); - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Timeout')), timeout) - ); - - return Promise.race([fetchPromise, timeoutPromise]); -} - -// Async iteration -async function processItems(items) { - for (const item of items) { - await processItem(item); - } -} - -async function processItem(item) { - const result = await transform(item); - await save(result); - return result; -} - -// Sequential async operations -async function sequentialProcess() { - const step1 = await doStep1(); - const step2 = await doStep2(step1); - const step3 = await doStep3(step2); - return step3; -} - -// Error-first callback (Node.js style) -const fs = require('fs'); - -fs.readFile('file.txt', (error, data) => { - if (error) { - console.error('Read failed:', error); - return; - } - console.log(data.toString()); -}); diff --git a/extension/examples/conditionals.js b/extension/examples/conditionals.js deleted file mode 100644 index 0905a6f..0000000 --- a/extension/examples/conditionals.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Example: Conditional Patterns - */ - -// Ternary -const status = isActive ? 'active' : 'inactive'; - -// Switch statement -function getGrade(score) { - switch (true) { - case score >= 90: - return 'A'; - case score >= 80: - return 'B'; - case score >= 70: - return 'C'; - default: - return 'F'; - } -} - -// If/else chain -function categorize(value) { - if (value < 0) { - return 'negative'; - } else if (value === 0) { - return 'zero'; - } else { - return 'positive'; - } -} - -// Early return (guard) -function process(data) { - if (!data) return null; - if (data.invalid) return null; - - return transform(data); -} - -// Logical OR for default -const name = userName || 'Guest'; diff --git a/extension/examples/error-handling.js b/extension/examples/error-handling.js deleted file mode 100644 index 056fd3d..0000000 --- a/extension/examples/error-handling.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Example: Error Handling Patterns - */ - -// Try/catch basic -function parseJSON(jsonString) { - try { - return JSON.parse(jsonString); - } catch (error) { - console.error('Parse failed:', error); - return null; - } -} - -// Result-like pattern -function divide(a, b) { - if (b === 0) { - return { error: 'Division by zero' }; - } - return { success: a / b }; -} - -// Throw error -function validateUser(user) { - if (!user.email) { - throw new Error('Email is required'); - } - if (!user.name) { - throw new Error('Name is required'); - } - return user; -} - -// Optional error return -function safeParse(input) { - try { - return JSON.parse(input); - } catch { - return undefined; - } -} diff --git a/extension/examples/null-checks.js b/extension/examples/null-checks.js deleted file mode 100644 index dcddad9..0000000 --- a/extension/examples/null-checks.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Example: Null Safety Patterns - * - * This file demonstrates various null checking patterns in JavaScript - * that can be transformed to ReScript's Option type. - */ - -// Basic null check -function getUserName(user) { - if (user !== null && user !== undefined) { - return user.name; - } - return 'Guest'; -} - -// Ternary null check -const displayName = user ? user.name : 'Anonymous'; - -// Optional chaining (ES2020) -const cityName = user?.address?.city; - -// Nullish coalescing -const username = user.name ?? 'Default User'; - -// Guard clause -function processUser(user) { - if (!user) { - return null; - } - - return { - id: user.id, - name: user.name, - email: user.email - }; -} - -// Typeof check -function getValue(data) { - if (typeof data !== 'undefined') { - return data.value; - } - return 0; -} - -// Logical AND short-circuit -const hasPermission = user && user.role && user.role === 'admin'; - -// Default with OR operator -const config = userConfig || {}; - -// Nested null checks -function getNestedValue(obj) { - if (obj && obj.data && obj.data.items && obj.data.items.length > 0) { - return obj.data.items[0]; - } - return null; -} - -// Array find with null check -const foundUser = users.find(u => u.id === userId); -if (foundUser) { - console.log(foundUser.name); -} diff --git a/extension/jest.config.js b/extension/jest.config.js deleted file mode 100644 index 10cd0d1..0000000 --- a/extension/jest.config.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - roots: ['/src'], - testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], - collectCoverageFrom: [ - 'src/**/*.ts', - '!src/**/*.d.ts', - '!src/test/**', - '!src/**/__tests__/**' - ], - coverageThreshold: { - global: { - branches: 70, - functions: 70, - lines: 70, - statements: 70 - } - } -}; diff --git a/extension/package.json b/extension/package.json deleted file mode 100644 index 971b6b4..0000000 --- a/extension/package.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "name": "rescript-evangeliser", - "displayName": "ReScript Evangeliser", - "description": "Learn ReScript through progressive code transformation - Celebrate good, minimize bad, show better", - "version": "0.1.0", - "publisher": "hyperpolymath", - "engines": { - "vscode": "^1.85.0" - }, - "categories": [ - "Education", - "Programming Languages", - "Snippets", - "Visualization" - ], - "keywords": [ - "rescript", - "reasonml", - "javascript", - "typescript", - "functional-programming", - "type-safety", - "code-transformation", - "learning", - "tutorial" - ], - "activationEvents": [ - "onLanguage:javascript", - "onLanguage:typescript", - "onLanguage:javascriptreact", - "onLanguage:typescriptreact" - ], - "main": "./out/extension.js", - "contributes": { - "commands": [ - { - "command": "rescript-evangeliser.transformSelection", - "title": "ReScript: Transform Selection", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.showTransformationPanel", - "title": "ReScript: Show Transformation Panel", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.analyzeFile", - "title": "ReScript: Analyze Current File", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.showPatternLibrary", - "title": "ReScript: Show Pattern Library", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.toggleView", - "title": "ReScript: Toggle View (RAW/FOLDED/GLYPHED/WYSIWYG)", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.nextPattern", - "title": "ReScript: Next Pattern", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.previousPattern", - "title": "ReScript: Previous Pattern", - "category": "ReScript Evangeliser" - }, - { - "command": "rescript-evangeliser.showTutorial", - "title": "ReScript: Start Tutorial", - "category": "ReScript Evangeliser" - } - ], - "keybindings": [ - { - "command": "rescript-evangeliser.transformSelection", - "key": "ctrl+shift+r", - "mac": "cmd+shift+r", - "when": "editorTextFocus" - }, - { - "command": "rescript-evangeliser.showTransformationPanel", - "key": "ctrl+shift+t", - "mac": "cmd+shift+t" - }, - { - "command": "rescript-evangeliser.toggleView", - "key": "ctrl+shift+v", - "mac": "cmd+shift+v", - "when": "rescriptEvangeliserPanelVisible" - } - ], - "configuration": { - "title": "ReScript Evangeliser", - "properties": { - "rescriptEvangeliser.defaultView": { - "type": "string", - "enum": ["RAW", "FOLDED", "GLYPHED", "WYSIWYG"], - "default": "RAW", - "description": "Default view mode for transformations" - }, - "rescriptEvangeliser.showNarratives": { - "type": "boolean", - "default": true, - "description": "Show encouraging narratives with transformations" - }, - "rescriptEvangeliser.autoDetectPatterns": { - "type": "boolean", - "default": true, - "description": "Automatically detect patterns in open files" - }, - "rescriptEvangeliser.difficultyLevel": { - "type": "string", - "enum": ["beginner", "intermediate", "advanced", "all"], - "default": "beginner", - "description": "Filter patterns by difficulty level" - }, - "rescriptEvangeliser.enableTelemetry": { - "type": "boolean", - "default": false, - "description": "Enable privacy-preserving usage analytics" - }, - "rescriptEvangeliser.performanceMode": { - "type": "boolean", - "default": false, - "description": "Enable performance optimizations for large files" - } - } - }, - "viewsContainers": { - "activitybar": [ - { - "id": "rescript-evangeliser", - "title": "ReScript Evangeliser", - "icon": "resources/icon.svg" - } - ] - }, - "views": { - "rescript-evangeliser": [ - { - "id": "rescriptPatternLibrary", - "name": "Pattern Library" - }, - { - "id": "rescriptLearningPath", - "name": "Learning Path" - }, - { - "id": "rescriptProgress", - "name": "Your Progress" - } - ] - } - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "pretest": "npm run compile && npm run lint", - "lint": "eslint src --ext ts", - "test": "node ./out/test/runTest.js", - "test:unit": "jest", - "test:coverage": "jest --coverage", - "package": "vsce package", - "deploy": "vsce publish" - }, - "devDependencies": { - "@types/vscode": "^1.85.0", - "@types/node": "^20.x", - "@types/jest": "^29.5.0", - "@typescript-eslint/eslint-plugin": "^6.15.0", - "@typescript-eslint/parser": "^6.15.0", - "@vscode/test-electron": "^2.3.8", - "eslint": "^8.56.0", - "jest": "^29.7.0", - "ts-jest": "^29.1.0", - "typescript": "^5.3.3", - "@vscode/vsce": "^2.22.0" - }, - "dependencies": { - "@babel/parser": "^7.23.6", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6" - } -} diff --git a/extension/src/extension.ts b/extension/src/extension.ts deleted file mode 100644 index 17972ab..0000000 --- a/extension/src/extension.ts +++ /dev/null @@ -1,537 +0,0 @@ -/** - * ReScript Evangeliser - VS Code Extension Entry Point - * - * Philosophy: "Celebrate good, minimize bad, show better" - * - Never shame developers for not knowing - * - Always celebrate their existing knowledge - * - Show how ReScript makes patterns even better - */ - -import * as vscode from 'vscode'; -import { detectPatterns, getPatternCount, getPatternStats, getPatternById } from './patterns/pattern-library'; -import { TransformationPanel } from './webview/transformation-panel'; -import { ExtensionConfig, LearningProgress, PatternMatch } from './types'; -import { createGlyphLegend } from './glyphs'; - -/** - * Extension state - */ -let transformationPanel: TransformationPanel | undefined; -let statusBarItem: vscode.StatusBarItem; -let learningProgress: LearningProgress; - -/** - * Activate the extension - */ -export function activate(context: vscode.ExtensionContext) { - console.log('🚀 ReScript Evangeliser is now active!'); - - // Initialize learning progress - learningProgress = context.globalState.get('learningProgress') || { - patternsCompleted: new Set(), - currentDifficulty: 'beginner', - totalTransformations: 0, - favoritePatterns: [], - customPatterns: [], - achievements: [], - startDate: new Date(), - lastActive: new Date() - }; - - // Create status bar item - statusBarItem = vscode.window.createStatusBarItem( - vscode.StatusBarAlignment.Right, - 100 - ); - statusBarItem.command = 'rescript-evangeliser.showPatternLibrary'; - updateStatusBar(); - statusBarItem.show(); - context.subscriptions.push(statusBarItem); - - // Register commands - registerCommands(context); - - // Set up file watchers - setupFileWatchers(context); - - // Show welcome message on first activation - const hasShownWelcome = context.globalState.get('hasShownWelcome'); - if (!hasShownWelcome) { - showWelcomeMessage(context); - } - - // Performance monitoring - const startTime = Date.now(); - console.log(`Extension activated in ${Date.now() - startTime}ms`); - - return { - // Extension API for testing - detectPatterns, - getPatternCount, - getConfig: () => getExtensionConfig() - }; -} - -/** - * Deactivate the extension - */ -export function deactivate() { - console.log('ReScript Evangeliser is now deactivated'); - - // Cleanup - if (transformationPanel) { - transformationPanel.dispose(); - } - - if (statusBarItem) { - statusBarItem.dispose(); - } -} - -/** - * Register all commands - */ -function registerCommands(context: vscode.ExtensionContext) { - // Transform Selection - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.transformSelection', async () => { - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showInformationMessage('No active editor'); - return; - } - - const selection = editor.selection; - const code = editor.document.getText(selection); - - if (!code) { - vscode.window.showInformationMessage('No code selected'); - return; - } - - await transformCode(code, context); - }) - ); - - // Show Transformation Panel - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.showTransformationPanel', async () => { - const editor = vscode.window.activeTextEditor; - const code = editor?.document.getText() || ''; - - if (!transformationPanel) { - transformationPanel = new TransformationPanel(context.extensionUri); - } - - transformationPanel.show(code); - }) - ); - - // Analyze Current File - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.analyzeFile', async () => { - await analyzeCurrentFile(context); - }) - ); - - // Show Pattern Library - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.showPatternLibrary', async () => { - await showPatternLibraryQuickPick(context); - }) - ); - - // Toggle View - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.toggleView', () => { - if (transformationPanel) { - transformationPanel.toggleView(); - } else { - vscode.window.showInformationMessage('Open transformation panel first'); - } - }) - ); - - // Next Pattern - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.nextPattern', () => { - if (transformationPanel) { - transformationPanel.nextPattern(); - } - }) - ); - - // Previous Pattern - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.previousPattern', () => { - if (transformationPanel) { - transformationPanel.previousPattern(); - } - }) - ); - - // Show Tutorial - context.subscriptions.push( - vscode.commands.registerCommand('rescript-evangeliser.showTutorial', async () => { - await showTutorial(context); - }) - ); -} - -/** - * Transform code and show results - */ -async function transformCode(code: string, context: vscode.ExtensionContext) { - const startTime = performance.now(); - - // Detect patterns - const patterns = detectPatterns(code); - - const analysisTime = performance.now() - startTime; - - if (patterns.length === 0) { - vscode.window.showInformationMessage( - '🤔 No ReScript patterns detected. Try selecting JavaScript code with common patterns like null checks, async/await, or array operations!' - ); - return; - } - - // Show first pattern - const pattern = patterns[0]; - - // Update progress - learningProgress.patternsCompleted.add(pattern.id); - learningProgress.totalTransformations++; - learningProgress.lastActive = new Date(); - await context.globalState.update('learningProgress', learningProgress); - - // Show transformation panel - if (!transformationPanel) { - transformationPanel = new TransformationPanel(context.extensionUri); - } - - transformationPanel.show(code, patterns); - - // Performance feedback - if (analysisTime < 50) { - console.log(`✅ Pattern detection: ${analysisTime.toFixed(2)}ms (excellent!)`); - } else if (analysisTime < 300) { - console.log(`✅ Pattern detection: ${analysisTime.toFixed(2)}ms (within target)`); - } else { - console.warn(`⚠️ Pattern detection: ${analysisTime.toFixed(2)}ms (above 300ms target)`); - } - - updateStatusBar(); -} - -/** - * Analyze current file - */ -async function analyzeCurrentFile(context: vscode.ExtensionContext) { - const editor = vscode.window.activeTextEditor; - - if (!editor) { - vscode.window.showInformationMessage('No active editor'); - return; - } - - const code = editor.document.getText(); - const patterns = detectPatterns(code); - - // Show analysis results - const panel = vscode.window.createWebviewPanel( - 'rescriptAnalysis', - 'ReScript Pattern Analysis', - vscode.ViewColumn.Two, - {} - ); - - const stats = getPatternStats(); - - panel.webview.html = ` - - - - - - Pattern Analysis - - - -

📊 Pattern Analysis Results

- -
-

Statistics

-

✨ Patterns Found: ${patterns.length}

-

📚 Total Patterns Available: ${stats.total}

-

💪 Your Progress: ${learningProgress.patternsCompleted.size}/${stats.total}

-
- -

Detected Patterns

- ${patterns.map(p => ` -
-

${p.glyphs.join(' ')} ${p.name}

-

Category: ${p.category}

-

Difficulty: ${p.difficulty}

-

✨ ${p.narrative.celebrate}

-
- `).join('')} - - ${patterns.length === 0 ? ` -

No patterns detected in this file. Try files with:

-
    -
  • Null checks (if x !== null)
  • -
  • Async/await functions
  • -
  • Array operations (map, filter, reduce)
  • -
  • Try/catch error handling
  • -
- ` : ''} - - - `; -} - -/** - * Show pattern library quick pick - */ -async function showPatternLibraryQuickPick(context: vscode.ExtensionContext) { - const config = getExtensionConfig(); - const stats = getPatternStats(); - - // Group patterns by category - const items: vscode.QuickPickItem[] = [ - { - label: '📊 Pattern Library Statistics', - description: `${stats.total} total patterns available`, - kind: vscode.QuickPickItemKind.Separator - }, - { - label: '🛡️ Null Safety', - description: `${stats.byCategory['null-safety'] || 0} patterns`, - detail: 'Option types, null checks, optional chaining' - }, - { - label: '⏳ Async Operations', - description: `${stats.byCategory['async'] || 0} patterns`, - detail: 'async/await, Promises, error handling' - }, - { - label: '✅ Error Handling', - description: `${stats.byCategory['error-handling'] || 0} patterns`, - detail: 'try/catch, Result types, error-first callbacks' - }, - { - label: '📚 Array Operations', - description: `${stats.byCategory['array-operations'] || 0} patterns`, - detail: 'map, filter, reduce, and more' - }, - { - label: '🌿 Conditionals', - description: `${stats.byCategory['conditionals'] || 0} patterns`, - detail: 'if/else, switch, ternary, pattern matching' - }, - { - label: '⚙️ Functional Programming', - description: `${stats.byCategory['functional'] || 0} patterns`, - detail: 'Pure functions, higher-order functions, composition' - }, - { - label: '🧩 Variants & Types', - description: `${stats.byCategory['variants'] || 0} patterns`, - detail: 'Union types, enums, state machines' - }, - { - label: '📦 Destructuring', - description: `${stats.byCategory['destructuring'] || 0} patterns`, - detail: 'Object/array destructuring, rest parameters' - }, - { - label: '📦 Modules', - description: `${stats.byCategory['modules'] || 0} patterns`, - detail: 'Imports, exports, module organization' - }, - { - label: '📝 Strings & Templates', - description: `${stats.byCategory['templates'] || 0} patterns`, - detail: 'Template literals, string interpolation' - }, - ]; - - const selected = await vscode.window.showQuickPick(items, { - placeHolder: 'Browse pattern library', - matchOnDescription: true, - matchOnDetail: true - }); - - if (selected) { - vscode.window.showInformationMessage(`Selected: ${selected.label}`); - } -} - -/** - * Show tutorial - */ -async function showTutorial(context: vscode.ExtensionContext) { - const panel = vscode.window.createWebviewPanel( - 'rescriptTutorial', - 'ReScript Tutorial', - vscode.ViewColumn.One, - {} - ); - - panel.webview.html = ` - - - - - - - -

🎓 Welcome to ReScript Evangeliser!

- -
-

Step 1: Select JavaScript Code

-

Open a JavaScript or TypeScript file, then select code with patterns like null checks, async/await, or array operations.

-
- -
-

Step 2: Transform

-

Press Ctrl+Shift+R (or Cmd+Shift+R on Mac) to see ReScript transformations!

-
- -
-

Step 3: Learn & Explore

-

Read the encouraging narrative explaining how ReScript makes your code even better!

-
- -
-

Philosophy: "Celebrate Good, Minimize Bad, Show Better"

-

We never shame you for not knowing ReScript. Your JavaScript is good! We just show how ReScript can make some things even better. 💙

-
- -

Ready? Try selecting some code and press Ctrl+Shift+R!

- - - `; -} - -/** - * Show welcome message - */ -async function showWelcomeMessage(context: vscode.ExtensionContext) { - const action = await vscode.window.showInformationMessage( - '🎉 Welcome to ReScript Evangeliser! Learn ReScript through encouraging code transformations.', - 'Show Tutorial', - 'Browse Patterns', - 'Dismiss' - ); - - if (action === 'Show Tutorial') { - vscode.commands.executeCommand('rescript-evangeliser.showTutorial'); - } else if (action === 'Browse Patterns') { - vscode.commands.executeCommand('rescript-evangeliser.showPatternLibrary'); - } - - await context.globalState.update('hasShownWelcome', true); -} - -/** - * Set up file watchers - */ -function setupFileWatchers(context: vscode.ExtensionContext) { - const config = getExtensionConfig(); - - if (!config.autoDetectPatterns) { - return; - } - - // Watch for active editor changes - vscode.window.onDidChangeActiveTextEditor(editor => { - if (editor && isJavaScriptFile(editor.document)) { - const patterns = detectPatterns(editor.document.getText()); - updateStatusBar(patterns.length); - } - }, null, context.subscriptions); - - // Watch for text document changes - vscode.workspace.onDidChangeTextDocument(event => { - const editor = vscode.window.activeTextEditor; - if (editor && editor.document === event.document && isJavaScriptFile(event.document)) { - // Debounce pattern detection - setTimeout(() => { - const patterns = detectPatterns(editor.document.getText()); - updateStatusBar(patterns.length); - }, 500); - } - }, null, context.subscriptions); -} - -/** - * Check if document is JavaScript/TypeScript - */ -function isJavaScriptFile(document: vscode.TextDocument): boolean { - return ['javascript', 'typescript', 'javascriptreact', 'typescriptreact'].includes( - document.languageId - ); -} - -/** - * Update status bar - */ -function updateStatusBar(patternCount?: number) { - const count = patternCount ?? 0; - const stats = getPatternStats(); - - statusBarItem.text = `$(lightbulb) ReScript: ${learningProgress.patternsCompleted.size}/${stats.total} patterns`; - statusBarItem.tooltip = `ReScript Evangeliser\n${count} patterns detected in current file\nClick to browse pattern library`; -} - -/** - * Get extension configuration - */ -function getExtensionConfig(): ExtensionConfig { - const config = vscode.workspace.getConfiguration('rescriptEvangeliser'); - - return { - defaultView: config.get('defaultView', 'RAW'), - showNarratives: config.get('showNarratives', true), - autoDetectPatterns: config.get('autoDetectPatterns', true), - difficultyLevel: config.get('difficultyLevel', 'beginner'), - enableTelemetry: config.get('enableTelemetry', false), - performanceMode: config.get('performanceMode', false), - customPatternPaths: config.get('customPatternPaths', []) - }; -} diff --git a/extension/src/glyphs.ts b/extension/src/glyphs.ts deleted file mode 100644 index 5010ccf..0000000 --- a/extension/src/glyphs.ts +++ /dev/null @@ -1,272 +0,0 @@ -/** - * Makaton-inspired glyph system for ReScript Evangeliser - * - * Glyphs transcend syntax to show semantic meaning. - * They help developers understand concepts visually, across language barriers. - */ - -import { Glyph } from './types'; - -/** - * Core set of 11 glyphs representing fundamental programming concepts - */ -export const GLYPHS: Record = { - TRANSFORM: { - symbol: '🔄', - name: 'Transform', - meaning: 'Data transformation or mapping operation', - semanticCategory: 'transformation', - usageExample: 'array.map(x => x * 2) becomes array->Array.map(x => x * 2)' - }, - - TARGET: { - symbol: '🎯', - name: 'Target', - meaning: 'Precise type targeting - no ambiguity', - semanticCategory: 'safety', - usageExample: 'Explicit type annotation ensures correctness' - }, - - SHIELD: { - symbol: '🛡️', - name: 'Shield', - meaning: 'Protection from null/undefined errors', - semanticCategory: 'safety', - usageExample: 'Option prevents null reference errors' - }, - - FLOW: { - symbol: '➡️', - name: 'Flow', - meaning: 'Pipe operator - data flows naturally left to right', - semanticCategory: 'flow', - usageExample: 'data->transform->filter->map reads like English' - }, - - BRANCH: { - symbol: '🌿', - name: 'Branch', - meaning: 'Pattern matching - exhaustive branching', - semanticCategory: 'flow', - usageExample: 'switch statement with all cases covered' - }, - - PACKAGE: { - symbol: '📦', - name: 'Package', - meaning: 'Module or encapsulated data structure', - semanticCategory: 'structure', - usageExample: 'Module or record type grouping related data' - }, - - LINK: { - symbol: '🔗', - name: 'Link', - meaning: 'Composition - connecting functions or modules', - semanticCategory: 'structure', - usageExample: 'Function composition or module composition' - }, - - CRYSTAL: { - symbol: '💎', - name: 'Crystal', - meaning: 'Immutability - unchanging, pure data', - semanticCategory: 'data', - usageExample: 'Immutable data structures prevent accidental mutation' - }, - - HOURGLASS: { - symbol: '⏳', - name: 'Hourglass', - meaning: 'Async operation - time-dependent computation', - semanticCategory: 'flow', - usageExample: 'async/await or Promise handling' - }, - - CHECKPOINT: { - symbol: '✅', - name: 'Checkpoint', - meaning: 'Result type - explicit success/failure handling', - semanticCategory: 'safety', - usageExample: 'Result makes error handling explicit' - }, - - LIGHT: { - symbol: '💡', - name: 'Light', - meaning: 'Type inference - compiler figures it out for you', - semanticCategory: 'safety', - usageExample: 'ReScript infers types without verbose annotations' - } -}; - -/** - * Additional glyphs for extended patterns - */ -export const EXTENDED_GLYPHS: Record = { - GEAR: { - symbol: '⚙️', - name: 'Gear', - meaning: 'Function - a working mechanism', - semanticCategory: 'transformation', - usageExample: 'Pure function that transforms inputs to outputs' - }, - - STACK: { - symbol: '📚', - name: 'Stack', - meaning: 'Collection or sequence of items', - semanticCategory: 'data', - usageExample: 'Array, List, or Stack data structure' - }, - - LOCK: { - symbol: '🔒', - name: 'Lock', - meaning: 'Encapsulation - private implementation', - semanticCategory: 'structure', - usageExample: 'Private module members or abstract types' - }, - - KEY: { - symbol: '🔑', - name: 'Key', - meaning: 'Access - public API or interface', - semanticCategory: 'structure', - usageExample: 'Public module signature or interface' - }, - - RECYCLE: { - symbol: '♻️', - name: 'Recycle', - meaning: 'Recursion - function calls itself', - semanticCategory: 'flow', - usageExample: 'Recursive function processing' - }, - - FILTER: { - symbol: '🔍', - name: 'Filter', - meaning: 'Selection or filtering operation', - semanticCategory: 'transformation', - usageExample: 'Array.filter or pattern matching with guards' - }, - - MERGE: { - symbol: '🔀', - name: 'Merge', - meaning: 'Combining or merging data', - semanticCategory: 'transformation', - usageExample: 'Object spread, record update, or concatenation' - }, - - WARNING: { - symbol: '⚠️', - name: 'Warning', - meaning: 'Potential issue or anti-pattern', - semanticCategory: 'safety', - usageExample: 'Code that could be improved for safety' - }, - - ROCKET: { - symbol: '🚀', - name: 'Rocket', - meaning: 'Performance optimization', - semanticCategory: 'transformation', - usageExample: 'Fast, optimized code generation' - }, - - PUZZLE: { - symbol: '🧩', - name: 'Puzzle', - meaning: 'Type composition - building complex types from simple ones', - semanticCategory: 'structure', - usageExample: 'Variant types, record types, or type composition' - } -}; - -/** - * Get all available glyphs - */ -export function getAllGlyphs(): Glyph[] { - return [...Object.values(GLYPHS), ...Object.values(EXTENDED_GLYPHS)]; -} - -/** - * Get glyph by symbol - */ -export function getGlyphBySymbol(symbol: string): Glyph | undefined { - return getAllGlyphs().find(g => g.symbol === symbol); -} - -/** - * Get glyphs by semantic category - */ -export function getGlyphsByCategory(category: Glyph['semanticCategory']): Glyph[] { - return getAllGlyphs().filter(g => g.semanticCategory === category); -} - -/** - * Annotate code with glyphs based on detected patterns - */ -export function annotateWithGlyphs(code: string, glyphSymbols: string[]): string { - const glyphBar = glyphSymbols.join(' '); - return `${glyphBar}\n${code}`; -} - -/** - * Create a glyph legend for educational purposes - */ -export function createGlyphLegend(): string { - const categories = ['safety', 'transformation', 'flow', 'structure', 'data'] as const; - - let legend = '# Glyph Legend\n\n'; - legend += 'Visual symbols representing programming concepts:\n\n'; - - for (const category of categories) { - const categoryGlyphs = getGlyphsByCategory(category); - if (categoryGlyphs.length === 0) continue; - - legend += `## ${category.charAt(0).toUpperCase() + category.slice(1)}\n\n`; - - for (const glyph of categoryGlyphs) { - legend += `- ${glyph.symbol} **${glyph.name}**: ${glyph.meaning}\n`; - legend += ` - Example: ${glyph.usageExample}\n\n`; - } - } - - return legend; -} - -/** - * Get glyphs for a specific pattern category - */ -export function getGlyphsForPattern( - patternCategory: string -): string[] { - const mapping: Record = { - 'null-safety': ['🛡️', '✅', '💎'], - 'async': ['⏳', '🔄', '➡️'], - 'error-handling': ['✅', '🛡️', '🌿'], - 'array-operations': ['🔄', '📚', '🔍'], - 'conditionals': ['🌿', '🎯', '🧩'], - 'destructuring': ['📦', '🔑', '🔀'], - 'defaults': ['🛡️', '💎', '🎯'], - 'functional': ['⚙️', '🔗', '💎'], - 'templates': ['🔄', '🔀', '➡️'], - 'arrow-functions': ['⚙️', '➡️', '🔄'], - 'variants': ['🧩', '🌿', '🎯'], - 'modules': ['📦', '🔒', '🔑'], - 'type-safety': ['🎯', '🛡️', '💡'], - 'immutability': ['💎', '🔒', '🛡️'], - 'pattern-matching': ['🌿', '🎯', '✅'], - 'pipe-operator': ['➡️', '🔄', '🔗'], - 'oop-to-fp': ['⚙️', '💎', '🔗'], - 'classes-to-records': ['📦', '💎', '🎯'], - 'inheritance-to-composition': ['🔗', '🧩', '📦'], - 'state-machines': ['🌿', '🧩', '✅'], - 'data-modeling': ['🧩', '📦', '🎯'] - }; - - return mapping[patternCategory] || ['💡']; -} diff --git a/extension/src/narrative.ts b/extension/src/narrative.ts deleted file mode 100644 index 7edb7b3..0000000 --- a/extension/src/narrative.ts +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Narrative generation for ReScript Evangeliser - * - * Philosophy: "Celebrate good, minimize bad, show better" - * - NEVER shame developers - * - ALWAYS celebrate what their JavaScript does well - * - Gently show how ReScript makes it even better - */ - -import { Narrative, Pattern, PatternCategory } from './types'; - -/** - * Generate an encouraging narrative for a pattern - */ -export function generateNarrative(pattern: Pattern): Narrative { - // Use pattern's built-in narrative if available - if (pattern.narrative) { - return pattern.narrative; - } - - // Generate dynamic narrative based on category - return generateCategoryNarrative(pattern.category, pattern.name); -} - -/** - * Generate narrative based on pattern category - */ -function generateCategoryNarrative(category: PatternCategory, patternName: string): Narrative { - const templates = NARRATIVE_TEMPLATES[category] || NARRATIVE_TEMPLATES['default']; - - return { - celebrate: pickRandom(templates.celebrate), - minimize: pickRandom(templates.minimize), - better: pickRandom(templates.better), - safety: pickRandom(templates.safety), - example: `See how the ${patternName} pattern works in ReScript!` - }; -} - -/** - * Narrative templates organized by category - */ -const NARRATIVE_TEMPLATES: Record = { - 'null-safety': { - celebrate: [ - "You're already thinking about null and undefined - that's great defensive programming!", - "Nice! You're checking for null values - you understand the importance of safety.", - "You've got null checks in place - you're already thinking like a type-safe developer!", - "Smart move checking for null! You know that data isn't always what you expect." - ], - minimize: [ - "The only small thing is that it's easy to forget one of these checks somewhere...", - "It's just a tiny bit verbose with all these conditional checks...", - "These checks work, though they do add a bit of cognitive overhead...", - "Nothing wrong with this approach, it's just that there's a way to make it even simpler..." - ], - better: [ - "ReScript's Option type makes null safety automatic - you literally can't forget a check!", - "With ReScript, the compiler ensures you handle the None case - no runtime surprises!", - "ReScript's Option type gives you the same safety with cleaner, more expressive code.", - "In ReScript, null safety is built into the type system - it's impossible to forget!" - ], - safety: [ - "The compiler won't let your code compile until you've handled both Some and None cases.", - "100% null-safe at compile time - no more 'Cannot read property of undefined' errors!", - "Type-level guarantee that you've considered the absence of a value.", - "ReScript eliminates an entire class of runtime errors related to null/undefined." - ] - }, - - 'async': { - celebrate: [ - "Async/await is a huge improvement over callback hell - you're writing modern JavaScript!", - "Great use of async/await! You understand asynchronous programming patterns.", - "Nice! You're already using async/await - you know how to handle asynchronous operations.", - "You've mastered one of JavaScript's most powerful features - async/await!" - ], - minimize: [ - "The only thing is that error handling can get a bit scattered with try/catch...", - "It works great, though tracking all the possible error states can be tricky...", - "This is solid code, there's just a way to make async errors more explicit...", - "Nothing wrong here, but error paths aren't always obvious in the type system..." - ], - better: [ - "ReScript makes async operations type-safe with Promise types that flow through your code!", - "With ReScript, your async functions have explicit return types - no surprises!", - "ReScript's type system tracks async operations and ensures you handle all cases.", - "In ReScript, async errors are part of the type signature - impossible to ignore!" - ], - safety: [ - "The compiler ensures you handle both success and failure cases for async operations.", - "Type-level tracking of async operations prevents race conditions and timing bugs.", - "Explicit Promise types make async behavior clear and prevent common mistakes.", - "ReScript's type system catches async errors at compile time, not runtime." - ] - }, - - 'error-handling': { - celebrate: [ - "Excellent! You're using try/catch - you know that errors need handling!", - "Great defensive programming! You're catching errors before they crash the app.", - "You understand that things can go wrong - that's mature error handling!", - "Nice! You're already thinking about the unhappy path - many developers forget this." - ], - minimize: [ - "The only small thing is that errors can sometimes slip through if you're not careful...", - "It works well, though it's easy to forget error handling in some code paths...", - "This is good, there's just a way to make errors impossible to ignore...", - "Nothing wrong with try/catch, but the type system doesn't help track errors..." - ], - better: [ - "ReScript's Result type makes errors explicit - they're part of your function signature!", - "With Result, you can't ignore errors - the compiler won't let you!", - "ReScript treats errors as values, making them composable and type-safe.", - "In ReScript, error handling is enforced by the type system - no forgotten catch blocks!" - ], - safety: [ - "The compiler ensures you handle both Ok and Error cases - no silent failures!", - "Result types make error handling explicit and impossible to ignore.", - "Type-safe error handling means errors are documented in function signatures.", - "ReScript eliminates the possibility of unhandled exceptions at compile time." - ] - }, - - 'array-operations': { - celebrate: [ - "Perfect! You're using array methods like map and filter - functional programming at its best!", - "Great use of array operations! You understand how to work with collections.", - "Nice! These array methods are exactly the right tool for transforming data.", - "You're writing functional code with map/filter/reduce - that's excellent!" - ], - minimize: [ - "The only thing is that these methods can sometimes be chained in ways that are hard to read...", - "It works perfectly, there's just a more readable way to express these transformations...", - "This is fine, though deeply nested callbacks can get a bit hard to follow...", - "Nothing wrong here, but there's a syntax that makes data flow even clearer..." - ], - better: [ - "ReScript's pipe operator lets you write these transformations in reading order!", - "With ReScript's ->, data transformations read like a sentence from left to right!", - "ReScript makes array operations more readable with the pipe operator and better inference.", - "In ReScript, you can chain operations naturally with -> and the compiler handles the rest!" - ], - safety: [ - "Type inference ensures your array transformations are always type-safe.", - "The compiler catches type mismatches in your data pipelines before runtime.", - "ReScript's array operations are fully typed - no runtime type errors!", - "Type-safe array operations prevent common mistakes like accessing undefined indices." - ] - }, - - 'conditionals': { - celebrate: [ - "Good use of conditionals! You're handling different cases in your logic.", - "Nice! You're thinking through the different paths your code can take.", - "Great! You understand that code needs to branch based on different conditions.", - "You're handling multiple cases - that's thorough programming!" - ], - minimize: [ - "The only thing is that it's possible to miss a case with if/else chains...", - "It works, though the compiler can't help you know if you've covered all cases...", - "This is solid, there's just a way to make sure you never forget a case...", - "Nothing wrong with if/else, but there's a more exhaustive way to handle cases..." - ], - better: [ - "ReScript's pattern matching ensures you handle ALL cases - the compiler checks!", - "With switch expressions, ReScript won't compile until all cases are covered!", - "ReScript's pattern matching is exhaustive - impossible to forget a case!", - "In ReScript, switch is an expression with compile-time exhaustiveness checking!" - ], - safety: [ - "Exhaustive pattern matching means every case is handled - no forgotten branches!", - "The compiler proves you've covered all possible values - no runtime surprises!", - "Pattern matching with exhaustiveness checking eliminates an entire class of bugs.", - "ReScript's switch won't compile until you've handled every possible case!" - ] - }, - - 'functional': { - celebrate: [ - "Excellent! You're using pure functions - you understand functional programming!", - "Great! These functions are predictable and testable - that's good design!", - "Nice functional code! You're avoiding side effects and mutations.", - "You're writing pure functions - that's a sign of a skilled developer!" - ], - minimize: [ - "The only thing is that JavaScript doesn't enforce purity - it's easy to slip...", - "It works great, though mutations can accidentally creep in...", - "This is solid functional code, there's just a way to make purity automatic...", - "Nothing wrong here, but JavaScript allows mutations that can break purity..." - ], - better: [ - "ReScript makes immutability the default - you have to explicitly opt into mutation!", - "With ReScript, functional programming is natural and enforced by the type system!", - "ReScript's immutable-by-default data structures prevent accidental side effects!", - "In ReScript, pure functional programming is the easy path, not the hard one!" - ], - safety: [ - "Immutability by default means no accidental mutations - safer concurrent code!", - "The type system ensures your functions are actually pure - no hidden side effects!", - "ReScript's functional features prevent entire categories of bugs related to shared state.", - "Compiler-enforced immutability makes your code more predictable and testable!" - ] - }, - - 'default': { - celebrate: [ - "You're already writing good JavaScript - this pattern works!", - "Nice! You understand this programming concept well.", - "Great code! You're using modern JavaScript features effectively.", - "You've got the right idea - this is solid programming!" - ], - minimize: [ - "There's just a small opportunity to make this even better...", - "It works perfectly, there's just a more type-safe way to express this...", - "Nothing wrong with this approach, but ReScript has a nice enhancement...", - "This is fine, there's just a way to get more compiler help..." - ], - better: [ - "ReScript brings type safety and better error messages to this pattern!", - "With ReScript, you get the same functionality with stronger guarantees!", - "ReScript makes this pattern more explicit and catches errors earlier!", - "In ReScript, this becomes even more clear and type-safe!" - ], - safety: [ - "The compiler provides stronger guarantees about correctness!", - "Type safety catches potential issues before they become runtime errors!", - "ReScript's type system helps prevent common mistakes in this pattern!", - "Compile-time checking means more confidence in your code!" - ] - } -}; - -/** - * Pick a random element from an array - */ -function pickRandom(array: T[]): T { - return array[Math.floor(Math.random() * array.length)]; -} - -/** - * Create a personalized narrative with user's name and pattern details - */ -export function personalizeNarrative( - narrative: Narrative, - userName?: string, - patternName?: string -): Narrative { - const prefix = userName ? `${userName}, ` : ''; - - return { - celebrate: prefix + narrative.celebrate, - minimize: narrative.minimize, - better: narrative.better, - safety: narrative.safety, - example: narrative.example - }; -} - -/** - * Format narrative for display - */ -export function formatNarrative(narrative: Narrative, format: 'plain' | 'markdown' | 'html' = 'markdown'): string { - if (format === 'plain') { - return ` -${narrative.celebrate} - -${narrative.minimize} - -${narrative.better} - -🛡️ Safety: ${narrative.safety} - -💡 ${narrative.example} - `.trim(); - } - - if (format === 'html') { - return ` -
-

✨ ${narrative.celebrate}

-

💭 ${narrative.minimize}

-

🚀 ${narrative.better}

-

🛡️ Safety: ${narrative.safety}

-

💡 ${narrative.example}

-
- `; - } - - // markdown (default) - return ` -✨ **You were close!** ${narrative.celebrate} - -💭 ${narrative.minimize} - -🚀 **Even better:** ${narrative.better} - -🛡️ **Safety:** ${narrative.safety} - -💡 *${narrative.example}* - `.trim(); -} - -/** - * Generate a success message for completed transformations - */ -export function generateSuccessMessage(patternName: string, difficulty: string): string { - const messages = [ - `Awesome! You've mastered the ${patternName} pattern! 🎉`, - `Great job! ${patternName} is now part of your ReScript toolkit! ✨`, - `Excellent work! You understand ${patternName} in ReScript! 🚀`, - `Well done! ${patternName} - another pattern conquered! 💪` - ]; - - return pickRandom(messages); -} - -/** - * Generate an encouraging hint for learning - */ -export function generateHint(pattern: Pattern): string { - const hints = [ - `💡 Tip: ${pattern.bestPractices[0] || 'Practice makes perfect!'}`, - `🎯 Remember: ${pattern.learningObjectives[0] || 'Focus on understanding the concept.'}`, - `⚠️ Watch out: ${pattern.commonMistakes[0] || 'Take your time and read the examples.'}`, - `✨ Pro tip: Try transforming your own code to really internalize this pattern!` - ]; - - return pickRandom(hints); -} diff --git a/extension/src/patterns/advanced-patterns.ts b/extension/src/patterns/advanced-patterns.ts deleted file mode 100644 index dfbf498..0000000 --- a/extension/src/patterns/advanced-patterns.ts +++ /dev/null @@ -1,745 +0,0 @@ -/** - * Advanced Patterns - Additional 25+ patterns - * Part 2 of the pattern library - */ - -import { Pattern } from '../types'; - -export const ADVANCED_PATTERNS: Pattern[] = [ - // ============================================================================ - // CONDITIONALS & PATTERN MATCHING (5 patterns) - // ============================================================================ - { - id: 'ternary-basic', - name: 'Ternary Operator', - category: 'conditionals', - difficulty: 'beginner', - jsPattern: /\w+\s*\?\s*[^:]+\s*:\s*[^;]+/, - confidence: 0.85, - jsExample: `const status = isActive ? 'active' : 'inactive';`, - rescriptExample: `let status = isActive ? "active" : "inactive"`, - narrative: { - celebrate: "Nice use of ternary - concise conditional logic!", - minimize: "Works great, though nested ternaries can get confusing...", - better: "ReScript supports ternary, but also has pattern matching for complex cases!", - safety: "Both branches must return the same type.", - example: "Type-safe ternary with guaranteed type consistency!" - }, - glyphs: ['🌿', '🎯'], - tags: ['ternary', 'conditional'], - relatedPatterns: ['switch-statement'], - learningObjectives: ['Ternary in ReScript', 'Type consistency'], - commonMistakes: ['Different types in branches'], - bestPractices: ['Use pattern matching for complex conditions'] - }, - - { - id: 'switch-statement', - name: 'Switch Statement', - category: 'conditionals', - difficulty: 'intermediate', - jsPattern: /switch\s*\([^)]+\)\s*{[\s\S]*?case\s+/, - confidence: 0.9, - jsExample: `switch (status) { - case 'pending': - return 'Waiting'; - case 'active': - return 'Running'; - default: - return 'Unknown'; -}`, - rescriptExample: `switch status { -| "pending" => "Waiting" -| "active" => "Running" -| _ => "Unknown" -}`, - narrative: { - celebrate: "Good use of switch for multiple cases!", - minimize: "Works well, though it's possible to forget a case...", - better: "ReScript's switch is an expression with exhaustiveness checking!", - safety: "Compiler ensures all cases are covered.", - example: "Exhaustive pattern matching prevents bugs!" - }, - glyphs: ['🌿', '🎯', '✅'], - tags: ['switch', 'pattern-matching'], - relatedPatterns: ['variant-types'], - learningObjectives: ['Switch expressions', 'Exhaustiveness'], - commonMistakes: ['Missing cases', 'Forgetting break'], - bestPractices: ['Use switch expressions, leverage exhaustiveness'] - }, - - { - id: 'if-else-chain', - name: 'If/Else Chain', - category: 'conditionals', - difficulty: 'beginner', - jsPattern: /if\s*\([^)]+\)\s*{[\s\S]*?}\s*else\s+if\s*\([^)]+\)/, - confidence: 0.8, - jsExample: `if (score >= 90) { - return 'A'; -} else if (score >= 80) { - return 'B'; -} else { - return 'C'; -}`, - rescriptExample: `if score >= 90 { - "A" -} else if score >= 80 { - "B" -} else { - "C" -}`, - narrative: { - celebrate: "Clear conditional logic with if/else!", - minimize: "It works, though pattern matching might be clearer...", - better: "ReScript's if/else are expressions that return values!", - safety: "All branches must return the same type.", - example: "If as expression with type safety!" - }, - glyphs: ['🌿', '➡️'], - tags: ['if-else', 'conditional'], - relatedPatterns: ['switch-statement'], - learningObjectives: ['If as expression'], - commonMistakes: ['Inconsistent return types'], - bestPractices: ['Consider switch for many conditions'] - }, - - { - id: 'early-return', - name: 'Early Return Pattern', - category: 'conditionals', - difficulty: 'intermediate', - jsPattern: /if\s*\([^)]+\)\s*{\s*return\s+[^}]+}\s*(?!else)/, - confidence: 0.75, - jsExample: `function process(data) { - if (!data) return null; - if (data.invalid) return null; - return transform(data); -}`, - rescriptExample: `let process = data => { - switch data { - | None => None - | Some(d) if d.invalid => None - | Some(d) => Some(transform(d)) - } -}`, - narrative: { - celebrate: "Smart use of early returns to handle edge cases!", - minimize: "Works well, though guards can be scattered...", - better: "ReScript's pattern matching with guards consolidates checks!", - safety: "Exhaustiveness ensures all cases handled.", - example: "Pattern matching with guards is cleaner!" - }, - glyphs: ['🌿', '🛡️'], - tags: ['early-return', 'guard'], - relatedPatterns: ['guard-clause'], - learningObjectives: ['Pattern guards', 'When clauses'], - commonMistakes: ['Missing edge cases'], - bestPractices: ['Use pattern matching over scattered guards'] - }, - - { - id: 'logical-or-default', - name: 'Logical OR for Default', - category: 'conditionals', - difficulty: 'beginner', - jsPattern: /\w+\s*\|\|\s*[\w'"]+/, - confidence: 0.7, - jsExample: `const name = userName || 'Guest';`, - rescriptExample: `let name = userName->Option.getOr("Guest")`, - narrative: { - celebrate: "Classic JavaScript pattern for defaults!", - minimize: "Works, but || can be tricky with falsy values (0, '')...", - better: "ReScript's Option.getOr is explicit about None vs falsy!", - safety: "Only handles None, not other falsy values.", - example: "Option.getOr is clearer than ||!" - }, - glyphs: ['🛡️', '🎯'], - tags: ['default', 'logical-or'], - relatedPatterns: ['nullish-coalescing'], - learningObjectives: ['Option vs falsy values'], - commonMistakes: ['Using || with 0 or empty string'], - bestPractices: ['Use Option.getOr or ??'] - }, - - // ============================================================================ - // FUNCTIONAL PROGRAMMING PATTERNS (6 patterns) - // ============================================================================ - { - id: 'pure-function', - name: 'Pure Function', - category: 'functional', - difficulty: 'intermediate', - jsPattern: (code: string) => { - // Simple heuristic: function with no this, no external mutations - return /function\s+\w+\([^)]*\)\s*{[^}]*return\s+/.test(code) && - !/\bthis\./.test(code); - }, - confidence: 0.6, - jsExample: `function add(a, b) { - return a + b; -}`, - rescriptExample: `let add = (a, b) => a + b`, - narrative: { - celebrate: "Perfect! Pure functions are the foundation of FP!", - minimize: "JavaScript doesn't enforce purity, so mutations can sneak in...", - better: "ReScript makes immutability default - purity is natural!", - safety: "Immutable by default prevents accidental mutations.", - example: "Pure functions with enforced immutability!" - }, - glyphs: ['⚙️', '💎', '🎯'], - tags: ['pure', 'function', 'immutable'], - relatedPatterns: ['immutable-data'], - learningObjectives: ['Pure functions', 'Immutability'], - commonMistakes: ['Hidden mutations', 'Side effects'], - bestPractices: ['Avoid mutations, return new values'] - }, - - { - id: 'higher-order-function', - name: 'Higher-Order Function', - category: 'functional', - difficulty: 'intermediate', - jsPattern: /function\s+\w+\([^)]*\)\s*{[\s\S]*?return\s+(?:function|\([^)]*\)\s*=>)/, - confidence: 0.75, - jsExample: `function multiplier(factor) { - return (number) => number * factor; -}`, - rescriptExample: `let multiplier = factor => number => number * factor`, - narrative: { - celebrate: "Excellent! Higher-order functions - advanced FP!", - minimize: "Works great, ReScript just has cleaner syntax...", - better: "ReScript's concise arrow syntax makes HOFs natural!", - safety: "Full type inference for nested functions.", - example: "Type-safe function composition!" - }, - glyphs: ['⚙️', '🔗', '🎯'], - tags: ['higher-order', 'closure', 'function'], - relatedPatterns: ['curry-function'], - learningObjectives: ['Higher-order functions', 'Closures'], - commonMistakes: ['Type annotations needed in complex cases'], - bestPractices: ['Embrace currying and composition'] - }, - - { - id: 'curry-function', - name: 'Curried Function', - category: 'functional', - difficulty: 'advanced', - jsPattern: /=>\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.8, - jsExample: `const add = a => b => a + b; -const add5 = add(5);`, - rescriptExample: `let add = (a, b) => a + b -let add5 = add(5)`, - narrative: { - celebrate: "Advanced! Currying enables partial application!", - minimize: "Manual currying in JS can be verbose...", - better: "ReScript automatically curries multi-parameter functions!", - safety: "Type system understands currying natively.", - example: "Built-in currying with full type safety!" - }, - glyphs: ['⚙️', '🔗', '🎯'], - tags: ['curry', 'partial-application'], - relatedPatterns: ['higher-order-function'], - learningObjectives: ['Currying', 'Partial application'], - commonMistakes: ['Not understanding automatic currying'], - bestPractices: ['Leverage automatic currying'] - }, - - { - id: 'compose-functions', - name: 'Function Composition', - category: 'functional', - difficulty: 'advanced', - jsPattern: /compose|pipe/, - confidence: 0.7, - jsExample: `const transform = compose( - trim, - toLowerCase, - removeSpaces -);`, - rescriptExample: `let transform = str => - str->trim->toLowerCase->removeSpaces`, - narrative: { - celebrate: "Expert level! Function composition for data pipelines!", - minimize: "Compose utilities work, but pipe operator is clearer...", - better: "ReScript's -> operator makes composition natural and readable!", - safety: "Type system ensures pipeline compatibility.", - example: "Native pipe operator for clean composition!" - }, - glyphs: ['🔗', '➡️', '⚙️'], - tags: ['compose', 'pipe', 'pipeline'], - relatedPatterns: ['pipe-operator'], - learningObjectives: ['Pipe operator', 'Data pipelines'], - commonMistakes: ['Type mismatches in pipeline'], - bestPractices: ['Use -> for left-to-right flow'] - }, - - { - id: 'immutable-data', - name: 'Immutable Data Patterns', - category: 'functional', - difficulty: 'intermediate', - jsPattern: /\.\.\.[\w.]+/, - confidence: 0.6, - jsExample: `const updated = { - ...user, - name: newName -};`, - rescriptExample: `let updated = { - ...user, - name: newName -}`, - narrative: { - celebrate: "Great! Using spread for immutable updates!", - minimize: "Spread works, but it's not enforced - mutations still possible...", - better: "ReScript enforces immutability - updates must use spread!", - safety: "Can't mutate records without explicit mutation marker.", - example: "Immutability by default prevents bugs!" - }, - glyphs: ['💎', '🔄', '📦'], - tags: ['immutable', 'spread', 'record'], - relatedPatterns: ['pure-function'], - learningObjectives: ['Record updates', 'Immutability'], - commonMistakes: ['Accidental mutations'], - bestPractices: ['Always use record spread for updates'] - }, - - { - id: 'recursion-pattern', - name: 'Recursion', - category: 'functional', - difficulty: 'advanced', - jsPattern: /function\s+(\w+)\s*\([^)]*\)\s*{[\s\S]*?\1\s*\(/, - confidence: 0.8, - jsExample: `function factorial(n) { - if (n <= 1) return 1; - return n * factorial(n - 1); -}`, - rescriptExample: `let rec factorial = n => - if n <= 1 { - 1 - } else { - n * factorial(n - 1) - }`, - narrative: { - celebrate: "Excellent recursion! FP at its core!", - minimize: "Works great, ReScript just requires 'rec' keyword...", - better: "ReScript's 'rec' makes recursion explicit and optimized!", - safety: "Tail-call optimization when possible.", - example: "Explicit recursion with optimizations!" - }, - glyphs: ['♻️', '⚙️', '🔄'], - tags: ['recursion', 'tail-call'], - relatedPatterns: ['array-reduce'], - learningObjectives: ['Recursion', 'Tail-call optimization'], - commonMistakes: ['Stack overflow without TCO'], - bestPractices: ['Use tail-recursion when possible'] - }, - - // ============================================================================ - // VARIANT & TYPE PATTERNS (5 patterns) - // ============================================================================ - { - id: 'union-type-simulation', - name: 'Union Type with Discriminator', - category: 'variants', - difficulty: 'intermediate', - jsPattern: /type\s*:\s*['"][\w-]+['"]/, - confidence: 0.7, - jsExample: `const result = { type: 'success', data: value }; -// or -const error = { type: 'error', message: msg };`, - rescriptExample: `type result<'a, 'e> = - | Ok('a) - | Error('e) - -let result = Ok(value)`, - narrative: { - celebrate: "Smart! Using type tags to simulate union types!", - minimize: "Works, but TypeScript can't enforce all cases are handled...", - better: "ReScript's variant types are native with exhaustiveness checking!", - safety: "Compiler ensures switch handles all variants.", - example: "First-class variant types with safety!" - }, - glyphs: ['🧩', '🌿', '🎯'], - tags: ['variant', 'union', 'discriminated'], - relatedPatterns: ['error-result-type'], - learningObjectives: ['Variant types', 'Pattern matching'], - commonMistakes: ['Misspelling type tags', 'Missing cases'], - bestPractices: ['Use variant types for tagged unions'] - }, - - { - id: 'enum-pattern', - name: 'Enum Simulation', - category: 'variants', - difficulty: 'beginner', - jsPattern: /const\s+\w+\s*=\s*{\s*[\w\s:'"]+,/, - confidence: 0.6, - jsExample: `const Status = { - PENDING: 'pending', - ACTIVE: 'active', - DONE: 'done' -};`, - rescriptExample: `type status = - | Pending - | Active - | Done`, - narrative: { - celebrate: "Good! Using objects as enums - a common JS pattern!", - minimize: "Works, but values are just strings - no type safety...", - better: "ReScript's variant types are true enums with compiler support!", - safety: "Can't use invalid enum values - compile error.", - example: "Type-safe enums with exhaustive matching!" - }, - glyphs: ['🧩', '🎯', '🔒'], - tags: ['enum', 'variant', 'constant'], - relatedPatterns: ['union-type-simulation'], - learningObjectives: ['Variant types as enums'], - commonMistakes: ['Using string constants instead of variants'], - bestPractices: ['Use variant types for enums'] - }, - - { - id: 'state-machine', - name: 'State Machine Pattern', - category: 'state-machines', - difficulty: 'advanced', - jsPattern: /state|transition|setState/, - confidence: 0.5, - jsExample: `let state = 'idle'; -function transition(event) { - if (state === 'idle' && event === 'start') { - state = 'running'; - } -}`, - rescriptExample: `type state = Idle | Running | Done - -type event = Start | Stop | Reset - -let transition = (state, event) => - switch (state, event) { - | (Idle, Start) => Running - | (Running, Stop) => Done - | (_, Reset) => Idle - | _ => state - }`, - narrative: { - celebrate: "Advanced! You're building a state machine!", - minimize: "Works, but invalid transitions aren't prevented...", - better: "ReScript's variants make state machines type-safe!", - safety: "Impossible states are impossible to represent.", - example: "Type-safe state machines with variants!" - }, - glyphs: ['🧩', '🌿', '🎯'], - tags: ['state-machine', 'variant', 'fsm'], - relatedPatterns: ['union-type-simulation'], - learningObjectives: ['State machines', 'Impossible states'], - commonMistakes: ['Invalid state transitions'], - bestPractices: ['Model states as variants'] - }, - - { - id: 'option-type-pattern', - name: 'Optional Values Pattern', - category: 'type-safety', - difficulty: 'intermediate', - jsPattern: (code) => code.includes('| undefined') || code.includes('| null'), - confidence: 0.6, - jsExample: `function findUser(id: number): User | undefined { - return users.find(u => u.id === id); -}`, - rescriptExample: `let findUser = (id: int): option => { - users->Array.find(u => u.id == id) -}`, - narrative: { - celebrate: "Good TypeScript! Using union with undefined for optionals!", - minimize: "Works, but | undefined must be in every signature...", - better: "ReScript's Option type is built-in and automatic!", - safety: "Option is the standard way to represent optionality.", - example: "First-class Option type for all nullable values!" - }, - glyphs: ['🛡️', '🧩', '💡'], - tags: ['option', 'type-safety', 'nullable'], - relatedPatterns: ['null-check-basic'], - learningObjectives: ['Option type', 'Type-safe optionality'], - commonMistakes: ['Forgetting | undefined'], - bestPractices: ['Always use Option for nullable values'] - }, - - { - id: 'type-guard', - name: 'Type Guard', - category: 'type-safety', - difficulty: 'intermediate', - jsPattern: /function\s+is\w+\([^)]+\):\s*\w+\s+is\s+\w+/, - confidence: 0.85, - jsExample: `function isString(value: unknown): value is string { - return typeof value === 'string'; -}`, - rescriptExample: `// ReScript has structural typing -// Type guards aren't needed - pattern matching suffices -switch value { -| String(s) => /* use s as string */ -| _ => /* other types */ -}`, - narrative: { - celebrate: "TypeScript type guards - you know advanced typing!", - minimize: "They work, but you need a function for each type check...", - better: "ReScript's pattern matching does type narrowing automatically!", - safety: "Pattern matching narrows types without explicit guards.", - example: "Built-in type narrowing through patterns!" - }, - glyphs: ['🎯', '🛡️', '🌿'], - tags: ['type-guard', 'type-safety', 'narrowing'], - relatedPatterns: ['switch-statement'], - learningObjectives: ['Pattern matching for types'], - commonMistakes: ['Unnecessary type guards'], - bestPractices: ['Use pattern matching over type guards'] - }, - - // ============================================================================ - // DESTRUCTURING & DATA ACCESS PATTERNS (4 patterns) - // ============================================================================ - { - id: 'object-destructuring', - name: 'Object Destructuring', - category: 'destructuring', - difficulty: 'beginner', - jsPattern: /(?:const|let|var)\s*{\s*[\w,\s]+}\s*=/, - confidence: 0.9, - jsExample: `const { name, age } = user;`, - rescriptExample: `let {name, age} = user`, - narrative: { - celebrate: "Great! Destructuring is clean and modern!", - minimize: "Works perfectly, ReScript syntax is nearly identical...", - better: "ReScript's destructuring is fully type-checked!", - safety: "Compiler ensures destructured fields exist.", - example: "Type-safe destructuring with compile-time checks!" - }, - glyphs: ['📦', '🔑', '🎯'], - tags: ['destructuring', 'object', 'record'], - relatedPatterns: ['array-destructuring'], - learningObjectives: ['Record destructuring'], - commonMistakes: ['Destructuring non-existent fields'], - bestPractices: ['Use destructuring for clean code'] - }, - - { - id: 'array-destructuring', - name: 'Array Destructuring', - category: 'destructuring', - difficulty: 'beginner', - jsPattern: /(?:const|let|var)\s*\[\s*[\w,\s]+\]\s*=/, - confidence: 0.9, - jsExample: `const [first, second] = items;`, - rescriptExample: `let [first, second] = items`, - narrative: { - celebrate: "Perfect array destructuring!", - minimize: "Works great, ReScript adds pattern matching power...", - better: "ReScript's destructuring integrates with pattern matching!", - safety: "Type system ensures correct number of elements.", - example: "Destructuring with exhaustive patterns!" - }, - glyphs: ['📚', '🔑', '🎯'], - tags: ['destructuring', 'array', 'pattern'], - relatedPatterns: ['object-destructuring'], - learningObjectives: ['Array patterns'], - commonMistakes: ['Wrong number of bindings'], - bestPractices: ['Match against array length'] - }, - - { - id: 'rest-parameters', - name: 'Rest Parameters', - category: 'destructuring', - difficulty: 'intermediate', - jsPattern: /\.\.\.\w+/, - confidence: 0.8, - jsExample: `function sum(...numbers) { - return numbers.reduce((a, b) => a + b, 0); -}`, - rescriptExample: `// ReScript doesn't have rest params -// Use array directly -let sum = numbers => { - numbers->Array.reduce(0, (a, b) => a + b) -}`, - narrative: { - celebrate: "Good use of rest parameters for variadic functions!", - minimize: "Works, but rest params can obscure the API...", - better: "ReScript prefers explicit arrays - clearer and type-safe!", - safety: "Explicit array types are clearer than rest params.", - example: "Explicit arrays over implicit rest!" - }, - glyphs: ['📚', '🔄', '⚙️'], - tags: ['rest', 'variadic', 'parameters'], - relatedPatterns: ['array-reduce'], - learningObjectives: ['Arrays vs rest parameters'], - commonMistakes: ['Overusing rest params'], - bestPractices: ['Use explicit array parameters'] - }, - - { - id: 'nested-destructuring', - name: 'Nested Destructuring', - category: 'destructuring', - difficulty: 'intermediate', - jsPattern: /{\s*\w+\s*:\s*{/, - confidence: 0.7, - jsExample: `const { user: { name, email } } = response;`, - rescriptExample: `let {user: {name, email}} = response`, - narrative: { - celebrate: "Advanced destructuring - very clean!", - minimize: "Works, but can fail if nested value is undefined...", - better: "ReScript's destructuring with Option handles missing nested values!", - safety: "Compiler tracks nested optional values.", - example: "Safe nested destructuring with Option!" - }, - glyphs: ['📦', '🔗', '🛡️'], - tags: ['destructuring', 'nested', 'record'], - relatedPatterns: ['object-destructuring'], - learningObjectives: ['Nested patterns', 'Optional fields'], - commonMistakes: ['Not handling undefined nested values'], - bestPractices: ['Use Option for nested optionals'] - }, - - // ============================================================================ - // MODULES & ORGANIZATION PATTERNS (3 patterns) - // ============================================================================ - { - id: 'es6-import', - name: 'ES6 Import', - category: 'modules', - difficulty: 'beginner', - jsPattern: /import\s+.*\s+from\s+['"]/, - confidence: 0.95, - jsExample: `import { useState } from 'react';`, - rescriptExample: `// ReScript modules are automatic -// Each file is a module -// Use module name directly: React.useState`, - narrative: { - celebrate: "Modern ES6 imports - you know module systems!", - minimize: "Works great, though import paths can be fragile...", - better: "ReScript's modules are automatic - no import needed!", - safety: "Compiler ensures all modules exist.", - example: "Automatic modules with compile-time verification!" - }, - glyphs: ['📦', '🔗', '🎯'], - tags: ['import', 'module', 'es6'], - relatedPatterns: ['es6-export'], - learningObjectives: ['Automatic modules', 'Module references'], - commonMistakes: ['Unnecessary imports'], - bestPractices: ['Each file is a module automatically'] - }, - - { - id: 'es6-export', - name: 'ES6 Export', - category: 'modules', - difficulty: 'beginner', - jsPattern: /export\s+(?:default|const|function|class)/, - confidence: 0.95, - jsExample: `export const add = (a, b) => a + b;`, - rescriptExample: `// All top-level bindings are exported -let add = (a, b) => a + b`, - narrative: { - celebrate: "Clean exports - good module design!", - minimize: "Works well, though managing exports can be tedious...", - better: "ReScript exports everything by default - simpler!", - safety: "Use .resi files to control public API.", - example: "Exports by default with interface files for privacy!" - }, - glyphs: ['📦', '🔑', '🎯'], - tags: ['export', 'module', 'public'], - relatedPatterns: ['es6-import'], - learningObjectives: ['Module exports', 'Interface files'], - commonMistakes: ['Forgetting exports'], - bestPractices: ['Use .resi for public API control'] - }, - - { - id: 'namespace-pattern', - name: 'Namespace Pattern', - category: 'modules', - difficulty: 'intermediate', - jsPattern: /const\s+\w+\s*=\s*{\s*[\w\s:,()=>{}]+};/, - confidence: 0.5, - jsExample: `const Utils = { - format: (str) => str.trim(), - parse: (num) => parseInt(num) -};`, - rescriptExample: `module Utils = { - let format = str => String.trim(str) - let parse = num => Int.fromString(num) -}`, - narrative: { - celebrate: "Good namespacing to organize related functions!", - minimize: "Works, but object namespaces aren't true modules...", - better: "ReScript's module system is first-class!", - safety: "Modules are checked at compile time.", - example: "First-class modules with type safety!" - }, - glyphs: ['📦', '🔒', '🎯'], - tags: ['namespace', 'module', 'organization'], - relatedPatterns: ['es6-export'], - learningObjectives: ['Module syntax', 'Module nesting'], - commonMistakes: ['Using objects instead of modules'], - bestPractices: ['Use proper modules, not object namespaces'] - }, - - // ============================================================================ - // STRING & TEMPLATE PATTERNS (2 patterns) - // ============================================================================ - { - id: 'template-literals', - name: 'Template Literals', - category: 'templates', - difficulty: 'beginner', - jsPattern: /`[^`]*\$\{[^}]+\}[^`]*`/, - confidence: 0.95, - jsExample: `const greeting = \`Hello, \${name}!\`;`, - rescriptExample: `let greeting = \`Hello, \${name}!\``, - narrative: { - celebrate: "Perfect! Template literals are so much better than concatenation!", - minimize: "Works exactly the same in ReScript...", - better: "ReScript supports template literals with full type safety!", - safety: "Interpolated values are type-checked.", - example: "Type-safe string interpolation!" - }, - glyphs: ['🔄', '➡️'], - tags: ['template', 'string', 'interpolation'], - relatedPatterns: ['string-concatenation'], - learningObjectives: ['Template literals in ReScript'], - commonMistakes: ['Complex expressions in templates'], - bestPractices: ['Keep template expressions simple'] - }, - - { - id: 'string-concatenation', - name: 'String Concatenation', - category: 'templates', - difficulty: 'beginner', - jsPattern: /['"].*['"]\s*\+\s*\w+\s*\+/, - confidence: 0.8, - jsExample: `const message = 'Hello, ' + name + '!';`, - rescriptExample: `let message = "Hello, " ++ name ++ "!" -// or better: -let message = \`Hello, \${name}!\``, - narrative: { - celebrate: "Classic string building - it works!", - minimize: "Concatenation can get verbose with many parts...", - better: "ReScript has ++ for concat, but template literals are cleaner!", - safety: "Type system ensures you're concatenating strings.", - example: "Type-safe string operations!" - }, - glyphs: ['🔗', '🔄'], - tags: ['string', 'concatenation'], - relatedPatterns: ['template-literals'], - learningObjectives: ['String operators', 'Template literals'], - commonMistakes: ['Using + instead of ++'], - bestPractices: ['Prefer template literals over ++ chains'] - }, - -]; diff --git a/extension/src/patterns/pattern-library.ts b/extension/src/patterns/pattern-library.ts deleted file mode 100644 index 155462d..0000000 --- a/extension/src/patterns/pattern-library.ts +++ /dev/null @@ -1,880 +0,0 @@ -/** - * Pattern Library for ReScript Evangeliser - * - * 50+ transformation patterns from JavaScript/TypeScript to ReScript - * Organized by category and difficulty level - */ - -import { Pattern, PatternCategory, DifficultyLevel } from '../types'; -import { getGlyphsForPattern } from '../glyphs'; -import { ADVANCED_PATTERNS } from './advanced-patterns'; - -/** - * Basic patterns - 29 fundamental transformation patterns - */ -const BASIC_PATTERNS: Pattern[] = [ - // ============================================================================ - // NULL SAFETY PATTERNS (8 patterns) - // ============================================================================ - { - id: 'null-check-basic', - name: 'Basic Null Check', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /if\s*\(\s*(\w+)\s*!==?\s*null\s*&&\s*\1\s*!==?\s*undefined\s*\)/, - confidence: 0.9, - jsExample: `if (user !== null && user !== undefined) { - console.log(user.name); -}`, - rescriptExample: `switch user { -| Some(u) => Js.log(u.name) -| None => () -}`, - narrative: { - celebrate: "You're checking for null and undefined - that's great defensive programming!", - minimize: "The only small thing is that it's easy to forget one of these checks somewhere...", - better: "ReScript's Option type makes null safety automatic - you literally can't forget a check!", - safety: "The compiler won't let your code compile until you've handled both Some and None cases.", - example: "Option types eliminate an entire class of 'Cannot read property of undefined' errors!" - }, - glyphs: getGlyphsForPattern('null-safety'), - tags: ['null', 'undefined', 'option', 'safety'], - relatedPatterns: ['null-check-ternary', 'optional-chaining'], - learningObjectives: [ - 'Understand Option<\'a> type', - 'Pattern matching for null safety', - 'Exhaustiveness checking' - ], - commonMistakes: [ - 'Forgetting to handle None case', - 'Using null instead of None' - ], - bestPractices: [ - 'Always use Option for nullable values', - 'Use pattern matching to handle all cases', - 'Prefer Option.map over explicit pattern matching' - ] - }, - - { - id: 'null-check-ternary', - name: 'Null Check Ternary', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /(\w+)\s*\?\s*(\w+)\.\w+\s*:\s*['"]\w+['"]|null|undefined/, - confidence: 0.85, - jsExample: `const name = user ? user.name : 'Guest';`, - rescriptExample: `let name = user->Option.mapOr("Guest", u => u.name)`, - narrative: { - celebrate: "Nice! You're using a ternary to handle the null case - that's concise!", - minimize: "It works great, though it can get verbose with nested checks...", - better: "ReScript's Option.mapOr does this in one line with type safety!", - safety: "The compiler ensures the default value matches the expected type.", - example: "Option.mapOr handles the None case automatically!" - }, - glyphs: ['🛡️', '🎯', '➡️'], - tags: ['ternary', 'default', 'option'], - relatedPatterns: ['null-check-basic', 'default-params'], - learningObjectives: ['Option.mapOr function', 'Type-safe defaults'], - commonMistakes: ['Wrong default type'], - bestPractices: ['Use Option.mapOr for simple defaults'] - }, - - { - id: 'optional-chaining', - name: 'Optional Chaining', - category: 'null-safety', - difficulty: 'intermediate', - jsPattern: /\w+\?\.[\w.?]+/, - confidence: 0.95, - jsExample: `const city = user?.address?.city;`, - rescriptExample: `let city = user->Option.flatMap(u => u.address) - ->Option.map(a => a.city)`, - narrative: { - celebrate: "Excellent! You're using optional chaining - you know modern JavaScript!", - minimize: "It's really nice, though the result can still be undefined...", - better: "ReScript's Option.flatMap chains safely and the type tells you it's optional!", - safety: "The type system tracks the optionality through the entire chain.", - example: "Chain Option operations with flatMap and map!" - }, - glyphs: ['🛡️', '🔗', '➡️'], - tags: ['optional-chaining', 'nested', 'option'], - relatedPatterns: ['null-check-basic'], - learningObjectives: ['Option.flatMap', 'Chaining optional values'], - commonMistakes: ['Using map instead of flatMap'], - bestPractices: ['Use flatMap for nested Options'] - }, - - { - id: 'nullish-coalescing', - name: 'Nullish Coalescing', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /\w+\s*\?\?\s*[\w'"]+/, - confidence: 0.9, - jsExample: `const name = userName ?? 'Anonymous';`, - rescriptExample: `let name = userName->Option.getOr("Anonymous")`, - narrative: { - celebrate: "Great use of the nullish coalescing operator - modern JavaScript!", - minimize: "It's perfect for defaults, though the type doesn't capture the optionality...", - better: "ReScript's Option.getOr is explicit about optionality in the type!", - safety: "The type signature tells you exactly when a value might be absent.", - example: "Option.getOr provides type-safe default values!" - }, - glyphs: ['🛡️', '🎯'], - tags: ['nullish', 'default', 'option'], - relatedPatterns: ['default-params'], - learningObjectives: ['Option.getOr', 'Default values'], - commonMistakes: ['Confusing with || operator'], - bestPractices: ['Use getOr for simple defaults'] - }, - - { - id: 'guard-clause', - name: 'Guard Clause for Null', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /if\s*\(\s*!\s*\w+\s*\)\s*{\s*return/, - confidence: 0.8, - jsExample: `if (!user) { - return null; -} -// use user`, - rescriptExample: `switch user { -| None => None -| Some(u) => /* use u */ -}`, - narrative: { - celebrate: "Good use of guard clauses - you're preventing null reference errors!", - minimize: "It works, though it's possible to forget a guard somewhere...", - better: "ReScript's pattern matching makes guards exhaustive - can't forget them!", - safety: "Compiler ensures all paths are handled.", - example: "Pattern matching replaces guard clauses with exhaustiveness!" - }, - glyphs: ['🛡️', '🌿'], - tags: ['guard', 'early-return', 'option'], - relatedPatterns: ['null-check-basic'], - learningObjectives: ['Pattern matching', 'Early returns with Option'], - commonMistakes: ['Forgetting to handle all cases'], - bestPractices: ['Use pattern matching over guards'] - }, - - { - id: 'undefined-check', - name: 'Typeof Undefined Check', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /typeof\s+\w+\s*!==?\s*['"]undefined['"]/, - confidence: 0.85, - jsExample: `if (typeof value !== 'undefined') { - return value; -}`, - rescriptExample: `switch value { -| Some(v) => Some(v) -| None => None -}`, - narrative: { - celebrate: "You're using typeof to check for undefined - defensive programming!", - minimize: "It works, but typeof checks are a bit verbose...", - better: "ReScript's Option type eliminates the need for typeof checks entirely!", - safety: "The type system prevents undefined values at compile time.", - example: "No more typeof needed - the type tells you everything!" - }, - glyphs: ['🛡️', '🎯'], - tags: ['typeof', 'undefined', 'option'], - relatedPatterns: ['null-check-basic'], - learningObjectives: ['Option replaces runtime checks'], - commonMistakes: ['Mixing null and undefined checks'], - bestPractices: ['Use Option, not typeof'] - }, - - { - id: 'logical-and-null', - name: 'Logical AND Null Check', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /\w+\s*&&\s*\w+\.\w+/, - confidence: 0.7, - jsExample: `const name = user && user.name;`, - rescriptExample: `let name = user->Option.map(u => u.name)`, - narrative: { - celebrate: "Smart use of && for short-circuit evaluation!", - minimize: "It's concise, though the result type isn't always clear...", - better: "ReScript's Option.map makes the optionality explicit in the type!", - safety: "The type system tracks when values might be absent.", - example: "Option.map safely extracts values from Option types!" - }, - glyphs: ['🛡️', '🔄'], - tags: ['logical-and', 'short-circuit', 'option'], - relatedPatterns: ['null-check-basic'], - learningObjectives: ['Option.map for transformations'], - commonMistakes: ['Forgetting result might be undefined'], - bestPractices: ['Prefer Option.map over && chains'] - }, - - { - id: 'default-empty-object', - name: 'Default Empty Object', - category: 'null-safety', - difficulty: 'intermediate', - jsPattern: /\w+\s*\|\|\s*\{\}/, - confidence: 0.75, - jsExample: `const config = userConfig || {};`, - rescriptExample: `let config = userConfig->Option.getOr(defaultConfig)`, - narrative: { - celebrate: "Good use of || for default values - you're preventing errors!", - minimize: "It works, though empty objects can cause issues elsewhere...", - better: "ReScript requires explicit defaults with proper types!", - safety: "The compiler ensures default values have the correct type.", - example: "Type-safe defaults prevent runtime errors!" - }, - glyphs: ['🛡️', '📦'], - tags: ['default', 'object', 'option'], - relatedPatterns: ['nullish-coalescing'], - learningObjectives: ['Type-safe default objects'], - commonMistakes: ['Using || with falsy values'], - bestPractices: ['Define proper default values'] - }, - - // ============================================================================ - // ASYNC PATTERNS (6 patterns) - // ============================================================================ - { - id: 'async-await-basic', - name: 'Async/Await', - category: 'async', - difficulty: 'intermediate', - jsPattern: /async\s+function|\basync\s*\(/, - confidence: 0.95, - jsExample: `async function fetchUser(id) { - const response = await fetch(\`/api/users/\${id}\`); - return await response.json(); -}`, - rescriptExample: `let fetchUser = async (id) => { - let response = await fetch(\`/api/users/\${id}\`) - await response->Response.json -}`, - narrative: { - celebrate: "Excellent! You're using async/await - much better than callbacks!", - minimize: "It's great, though error handling can get scattered...", - better: "ReScript has async/await with type-safe Promises!", - safety: "Promise types are checked at compile time.", - example: "Type-safe async operations prevent common Promise mistakes!" - }, - glyphs: ['⏳', '🔄', '➡️'], - tags: ['async', 'await', 'promise'], - relatedPatterns: ['promise-then', 'try-catch-async'], - learningObjectives: ['Async/await in ReScript', 'Promise types'], - commonMistakes: ['Forgetting await', 'Missing error handling'], - bestPractices: ['Always handle Promise rejections'] - }, - - { - id: 'promise-then', - name: 'Promise Then Chain', - category: 'async', - difficulty: 'beginner', - jsPattern: /\.then\s*\([^)]+\)/, - confidence: 0.9, - jsExample: `fetch('/api/data') - .then(res => res.json()) - .then(data => console.log(data));`, - rescriptExample: `fetch("/api/data") -->Promise.then(res => res->Response.json) -->Promise.then(data => { - Js.log(data) - Promise.resolve() -})`, - narrative: { - celebrate: "Good use of Promise chains - you understand asynchronous flow!", - minimize: "It works well, though deeply nested thens can be hard to read...", - better: "ReScript's Promise.then with pipe operator makes chains more readable!", - safety: "Promise types are tracked through the chain.", - example: "Type-safe Promise chains with pipe operator!" - }, - glyphs: ['⏳', '🔗', '➡️'], - tags: ['promise', 'then', 'chain'], - relatedPatterns: ['async-await-basic'], - learningObjectives: ['Promise.then', 'Promise chaining'], - commonMistakes: ['Forgetting to return Promise'], - bestPractices: ['Consider async/await for readability'] - }, - - { - id: 'promise-all', - name: 'Promise.all', - category: 'async', - difficulty: 'intermediate', - jsPattern: /Promise\.all\s*\(/, - confidence: 0.95, - jsExample: `const [users, posts] = await Promise.all([ - fetchUsers(), - fetchPosts() -]);`, - rescriptExample: `let (users, posts) = await Promise.all2(( - fetchUsers(), - fetchPosts() -))`, - narrative: { - celebrate: "Excellent! You're running promises in parallel - that's optimal!", - minimize: "Perfect approach, ReScript just makes the types more explicit...", - better: "ReScript's Promise.all2/all3 have type-safe tuple returns!", - safety: "The compiler knows the exact types of all results.", - example: "Type-safe parallel async operations!" - }, - glyphs: ['⏳', '🔀', '🎯'], - tags: ['promise', 'parallel', 'all'], - relatedPatterns: ['async-await-basic'], - learningObjectives: ['Promise.all with types', 'Parallel execution'], - commonMistakes: ['Using sequential await instead of parallel'], - bestPractices: ['Use Promise.all for independent operations'] - }, - - { - id: 'try-catch-async', - name: 'Async Try/Catch', - category: 'async', - difficulty: 'intermediate', - jsPattern: /try\s*{[^}]*await[^}]*}\s*catch/s, - confidence: 0.85, - jsExample: `try { - const data = await fetchData(); - return data; -} catch (error) { - console.error(error); - return null; -}`, - rescriptExample: `try { - let data = await fetchData() - data -} catch { -| Js.Exn.Error(e) => { - Js.Console.error(e) - None - } -}`, - narrative: { - celebrate: "Great error handling with try/catch in async functions!", - minimize: "It works, though the error type isn't always clear...", - better: "ReScript's try/catch with pattern matching catches specific errors!", - safety: "Pattern matching on error types is exhaustive.", - example: "Type-safe error handling in async code!" - }, - glyphs: ['⏳', '✅', '🛡️'], - tags: ['async', 'try-catch', 'error'], - relatedPatterns: ['error-result-type'], - learningObjectives: ['Async error handling', 'Exception patterns'], - commonMistakes: ['Catching all errors without handling'], - bestPractices: ['Consider Result type for expected errors'] - }, - - { - id: 'promise-catch', - name: 'Promise Catch', - category: 'async', - difficulty: 'beginner', - jsPattern: /\.catch\s*\([^)]+\)/, - confidence: 0.9, - jsExample: `fetchData() - .then(data => process(data)) - .catch(error => console.error(error));`, - rescriptExample: `fetchData() -->Promise.then(data => process(data)) -->Promise.catch(error => { - Js.Console.error(error) - Promise.resolve(defaultValue) -})`, - narrative: { - celebrate: "Good! You're handling Promise rejections with catch!", - minimize: "It works, though error recovery isn't always type-safe...", - better: "ReScript's Promise.catch ensures recovery values match expected types!", - safety: "The compiler checks that catch returns the right Promise type.", - example: "Type-safe error recovery in Promise chains!" - }, - glyphs: ['⏳', '✅', '🔄'], - tags: ['promise', 'catch', 'error'], - relatedPatterns: ['try-catch-async'], - learningObjectives: ['Promise error handling'], - commonMistakes: ['Not returning a Promise from catch'], - bestPractices: ['Always handle Promise rejections'] - }, - - { - id: 'promise-race', - name: 'Promise.race', - category: 'async', - difficulty: 'advanced', - jsPattern: /Promise\.race\s*\(/, - confidence: 0.95, - jsExample: `const result = await Promise.race([ - fetchFromCache(), - fetchFromNetwork() -]);`, - rescriptExample: `let result = await Promise.race([ - fetchFromCache(), - fetchFromNetwork() -])`, - narrative: { - celebrate: "Advanced! You're racing promises - great for timeout patterns!", - minimize: "It works perfectly, ReScript just adds type safety...", - better: "ReScript's Promise.race ensures all promises have compatible types!", - safety: "The compiler verifies all promises return the same type.", - example: "Type-safe Promise racing for performance patterns!" - }, - glyphs: ['⏳', '🚀', '🎯'], - tags: ['promise', 'race', 'performance'], - relatedPatterns: ['promise-all'], - learningObjectives: ['Promise.race', 'Timeout patterns'], - commonMistakes: ['Racing incompatible Promise types'], - bestPractices: ['Use race for timeouts and fallbacks'] - }, - - // ============================================================================ - // ERROR HANDLING PATTERNS (5 patterns) - // ============================================================================ - { - id: 'try-catch-basic', - name: 'Basic Try/Catch', - category: 'error-handling', - difficulty: 'beginner', - jsPattern: /try\s*{[^}]+}\s*catch\s*\([^)]*\)\s*{/s, - confidence: 0.9, - jsExample: `try { - const result = riskyOperation(); - return result; -} catch (error) { - console.error(error); - return null; -}`, - rescriptExample: `switch riskyOperation() { -| result => Ok(result) -| exception Js.Exn.Error(e) => Error(e) -}`, - narrative: { - celebrate: "Excellent error handling with try/catch - you're thinking defensively!", - minimize: "It works great, though errors aren't part of the function signature...", - better: "ReScript's Result type makes errors explicit in the type!", - safety: "Callers must handle both Ok and Error cases.", - example: "Result makes error handling impossible to ignore!" - }, - glyphs: ['✅', '🛡️', '🌿'], - tags: ['try-catch', 'error', 'result'], - relatedPatterns: ['error-result-type'], - learningObjectives: ['Result type', 'Explicit error handling'], - commonMistakes: ['Ignoring error cases'], - bestPractices: ['Use Result for expected errors'] - }, - - { - id: 'error-result-type', - name: 'Result Type Pattern', - category: 'error-handling', - difficulty: 'intermediate', - jsPattern: /return\s*{\s*(?:success|ok|error)\s*:/, - confidence: 0.7, - jsExample: `function divide(a, b) { - if (b === 0) { - return { error: 'Division by zero' }; - } - return { success: a / b }; -}`, - rescriptExample: `let divide = (a, b) => { - if b == 0 { - Error("Division by zero") - } else { - Ok(a / b) - } -}`, - narrative: { - celebrate: "Smart! You're using an object to represent success/error - that's Result-like!", - minimize: "It works, though the shape isn't enforced by types...", - better: "ReScript's built-in Result type is exactly this, but type-safe!", - safety: "The compiler ensures you handle both Ok and Error.", - example: "Result<'a, 'error> is a first-class type!" - }, - glyphs: ['✅', '🌿', '🎯'], - tags: ['result', 'error', 'success'], - relatedPatterns: ['try-catch-basic'], - learningObjectives: ['Result type usage', 'Error as value'], - commonMistakes: ['Inconsistent error shapes'], - bestPractices: ['Always use Result for fallible operations'] - }, - - { - id: 'throw-error', - name: 'Throw Error', - category: 'error-handling', - difficulty: 'beginner', - jsPattern: /throw\s+new\s+Error\s*\(/, - confidence: 0.95, - jsExample: `if (!isValid) { - throw new Error('Invalid input'); -}`, - rescriptExample: `if !isValid { - Error("Invalid input") -} else { - Ok(value) -}`, - narrative: { - celebrate: "You're throwing errors for invalid states - good defensive programming!", - minimize: "It works, but exceptions aren't visible in function signatures...", - better: "ReScript prefers Result types that make errors explicit!", - safety: "Errors in the return type can't be ignored by callers.", - example: "Result types document errors in function signatures!" - }, - glyphs: ['✅', '🛡️'], - tags: ['throw', 'error', 'exception'], - relatedPatterns: ['error-result-type'], - learningObjectives: ['Errors as values vs exceptions'], - commonMistakes: ['Throwing exceptions for expected errors'], - bestPractices: ['Use Result for expected errors, exceptions for bugs'] - }, - - { - id: 'error-first-callback', - name: 'Error-First Callback', - category: 'error-handling', - difficulty: 'intermediate', - jsPattern: /\(\s*err(?:or)?\s*,\s*\w+\s*\)\s*=>\s*{\s*if\s*\(\s*err/, - confidence: 0.8, - jsExample: `readFile(path, (error, data) => { - if (error) { - console.error(error); - return; - } - process(data); -});`, - rescriptExample: `readFile(path, result => { - switch result { - | Error(err) => Js.Console.error(err) - | Ok(data) => process(data) - } -})`, - narrative: { - celebrate: "Classic Node.js pattern - you know error-first callbacks!", - minimize: "It works, though it's easy to forget the error check...", - better: "ReScript's Result type with pattern matching makes the check mandatory!", - safety: "Compiler won't let you access data without checking for errors.", - example: "Pattern matching ensures error handling!" - }, - glyphs: ['✅', '🌿', '⚙️'], - tags: ['callback', 'error-first', 'node'], - relatedPatterns: ['error-result-type'], - learningObjectives: ['Result in callbacks', 'Error handling patterns'], - commonMistakes: ['Forgetting to check error parameter'], - bestPractices: ['Convert callbacks to Promises or Result'] - }, - - { - id: 'optional-error', - name: 'Optional Error Return', - category: 'error-handling', - difficulty: 'intermediate', - jsPattern: /return\s+(?:null|undefined)\s*;[\s\S]*catch/, - confidence: 0.6, - jsExample: `function parse(input) { - try { - return JSON.parse(input); - } catch { - return null; - } -}`, - rescriptExample: `let parse = input => { - try { - Ok(JSON.parse(input)) - } catch { - | _ => Error("Parse failed") - } -}`, - narrative: { - celebrate: "You're returning null for errors - a simple error signaling approach!", - minimize: "It works, though callers might forget to check for null...", - better: "ReScript's Result type makes the possibility of failure explicit!", - safety: "The type system forces callers to handle both success and failure.", - example: "Result types are self-documenting error handling!" - }, - glyphs: ['✅', '🛡️', 'Option'], - tags: ['null', 'error', 'result'], - relatedPatterns: ['error-result-type', 'null-check-basic'], - learningObjectives: ['Result vs Option for errors'], - commonMistakes: ['Using null instead of Result'], - bestPractices: ['Use Result to carry error information'] - }, - - // ============================================================================ - // ARRAY OPERATION PATTERNS (8 patterns) - // ============================================================================ - { - id: 'array-map', - name: 'Array.map', - category: 'array-operations', - difficulty: 'beginner', - jsPattern: /\.map\s*\(\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.95, - jsExample: `const doubled = numbers.map(n => n * 2);`, - rescriptExample: `let doubled = numbers->Array.map(n => n * 2)`, - narrative: { - celebrate: "Perfect! Array.map is functional programming at its best!", - minimize: "Nothing wrong here, ReScript just adds the pipe operator...", - better: "ReScript's pipe operator makes data flow left-to-right!", - safety: "Type inference ensures the transformation is type-safe.", - example: "The -> operator makes transformations read like sentences!" - }, - glyphs: ['🔄', '➡️', '📚'], - tags: ['array', 'map', 'transform'], - relatedPatterns: ['array-filter', 'array-reduce', 'pipe-operator'], - learningObjectives: ['Array.map', 'Pipe operator'], - commonMistakes: ['Side effects in map'], - bestPractices: ['Keep map pure, use pipe for readability'] - }, - - { - id: 'array-filter', - name: 'Array.filter', - category: 'array-operations', - difficulty: 'beginner', - jsPattern: /\.filter\s*\(\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.95, - jsExample: `const evens = numbers.filter(n => n % 2 === 0);`, - rescriptExample: `let evens = numbers->Array.filter(n => mod(n, 2) == 0)`, - narrative: { - celebrate: "Great use of filter - functional programming done right!", - minimize: "Works perfectly, ReScript adds type-safe predicates...", - better: "ReScript's type system ensures your predicate always returns bool!", - safety: "The compiler checks that filter predicates return boolean.", - example: "Type-safe filtering with pipe operator!" - }, - glyphs: ['🔍', '➡️', '📚'], - tags: ['array', 'filter', 'predicate'], - relatedPatterns: ['array-map'], - learningObjectives: ['Array.filter', 'Boolean predicates'], - commonMistakes: ['Predicate returning non-boolean'], - bestPractices: ['Keep predicates pure and simple'] - }, - - { - id: 'array-reduce', - name: 'Array.reduce', - category: 'array-operations', - difficulty: 'intermediate', - jsPattern: /\.reduce\s*\(\s*\([^)]*\)\s*=>/, - confidence: 0.9, - jsExample: `const sum = numbers.reduce((acc, n) => acc + n, 0);`, - rescriptExample: `let sum = numbers->Array.reduce(0, (acc, n) => acc + n)`, - narrative: { - celebrate: "Excellent! Array.reduce is a powerful functional pattern!", - minimize: "It's great, though the parameter order can be confusing...", - better: "ReScript puts the initial value first - more intuitive!", - safety: "Type inference ensures accumulator and result types match.", - example: "Type-safe reduce with clear parameter order!" - }, - glyphs: ['🔄', '➡️', '⚙️'], - tags: ['array', 'reduce', 'fold'], - relatedPatterns: ['array-map'], - learningObjectives: ['Array.reduce', 'Accumulator patterns'], - commonMistakes: ['Wrong initial value type'], - bestPractices: ['Consider specialized functions like sum, join'] - }, - - { - id: 'array-find', - name: 'Array.find', - category: 'array-operations', - difficulty: 'beginner', - jsPattern: /\.find\s*\(\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.9, - jsExample: `const user = users.find(u => u.id === userId);`, - rescriptExample: `let user = users->Array.find(u => u.id == userId)`, - narrative: { - celebrate: "Good! You're using find to search arrays efficiently!", - minimize: "It works, though the result can be undefined...", - better: "ReScript's Array.find returns Option to handle 'not found' safely!", - safety: "The Option return type forces you to handle the not-found case.", - example: "Option<'a> makes missing values explicit!" - }, - glyphs: ['🔍', '🛡️', '📚'], - tags: ['array', 'find', 'search'], - relatedPatterns: ['null-check-basic'], - learningObjectives: ['Array.find with Option'], - commonMistakes: ['Not checking for undefined'], - bestPractices: ['Pattern match on find result'] - }, - - { - id: 'array-some', - name: 'Array.some', - category: 'array-operations', - difficulty: 'beginner', - jsPattern: /\.some\s*\(\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.9, - jsExample: `const hasAdmin = users.some(u => u.role === 'admin');`, - rescriptExample: `let hasAdmin = users->Array.some(u => u.role == "admin")`, - narrative: { - celebrate: "Perfect use of some for existence checks!", - minimize: "Nothing to improve here, works great...", - better: "ReScript's Array.some is identical but fully type-safe!", - safety: "Predicate must return bool, checked by compiler.", - example: "Type-safe existence checking!" - }, - glyphs: ['🔍', '✅', '📚'], - tags: ['array', 'some', 'exists'], - relatedPatterns: ['array-every'], - learningObjectives: ['Array.some for existence'], - commonMistakes: ['Using find when some is clearer'], - bestPractices: ['Use some for yes/no questions'] - }, - - { - id: 'array-every', - name: 'Array.every', - category: 'array-operations', - difficulty: 'beginner', - jsPattern: /\.every\s*\(\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.9, - jsExample: `const allValid = items.every(item => item.isValid);`, - rescriptExample: `let allValid = items->Array.every(item => item.isValid)`, - narrative: { - celebrate: "Excellent! Using every for validation - clean and functional!", - minimize: "Perfect approach, ReScript just adds type safety...", - better: "ReScript ensures your predicate is always boolean!", - safety: "Type system prevents non-boolean predicates.", - example: "Type-safe universal quantification!" - }, - glyphs: ['✅', '🎯', '📚'], - tags: ['array', 'every', 'validation'], - relatedPatterns: ['array-some'], - learningObjectives: ['Array.every for validation'], - commonMistakes: ['Confusing with some'], - bestPractices: ['Use every for validation logic'] - }, - - { - id: 'array-flatmap', - name: 'Array.flatMap', - category: 'array-operations', - difficulty: 'intermediate', - jsPattern: /\.flatMap\s*\(\s*(?:\w+|\([^)]*\))\s*=>/, - confidence: 0.9, - jsExample: `const allTags = posts.flatMap(post => post.tags);`, - rescriptExample: `let allTags = posts->Array.flatMap(post => post.tags)`, - narrative: { - celebrate: "Advanced! flatMap combines map and flatten - very functional!", - minimize: "Great pattern, ReScript makes it even more type-safe...", - better: "ReScript's flatMap ensures nested array types are correct!", - safety: "Type system tracks the nesting and flattening.", - example: "Type-safe flattening of nested arrays!" - }, - glyphs: ['🔄', '🔀', '📚'], - tags: ['array', 'flatmap', 'flatten'], - relatedPatterns: ['array-map'], - learningObjectives: ['Array.flatMap', 'Monadic operations'], - commonMistakes: ['Using map when flatMap is needed'], - bestPractices: ['Use flatMap for nested array operations'] - }, - - { - id: 'for-loop-to-map', - name: 'For Loop to Map', - category: 'array-operations', - difficulty: 'beginner', - jsPattern: /for\s*\(\s*(?:let|const|var)\s+\w+\s+of\s+\w+\s*\)\s*{[\s\S]*?\.push\s*\(/, - confidence: 0.7, - jsExample: `const result = []; -for (const item of items) { - result.push(transform(item)); -}`, - rescriptExample: `let result = items->Array.map(item => transform(item))`, - narrative: { - celebrate: "You're transforming arrays systematically - good logic!", - minimize: "It works, though it's a bit imperative with mutation...", - better: "ReScript's Array.map does this functionally without mutation!", - safety: "Immutable by default prevents accidental side effects.", - example: "Functional transformations are safer and clearer!" - }, - glyphs: ['🔄', '💎', '➡️'], - tags: ['for-loop', 'map', 'transform'], - relatedPatterns: ['array-map'], - learningObjectives: ['Functional vs imperative', 'Array.map'], - commonMistakes: ['Mutating in loops'], - bestPractices: ['Prefer map over imperative loops'] - }, - -]; - -/** - * Complete pattern library - 56+ total patterns - * Combines basic patterns (29) with advanced patterns (27+) - */ -export const PATTERN_LIBRARY: Pattern[] = [ - ...BASIC_PATTERNS, - ...ADVANCED_PATTERNS -]; - -/** - * Get pattern by ID - */ -export function getPatternById(id: string): Pattern | undefined { - return PATTERN_LIBRARY.find(p => p.id === id); -} - -/** - * Get patterns by category - */ -export function getPatternsByCategory(category: PatternCategory): Pattern[] { - return PATTERN_LIBRARY.filter(p => p.category === category); -} - -/** - * Get patterns by difficulty - */ -export function getPatternsByDifficulty(difficulty: DifficultyLevel): Pattern[] { - return PATTERN_LIBRARY.filter(p => p.difficulty === difficulty); -} - -/** - * Detect patterns in code - */ -export function detectPatterns(code: string): Pattern[] { - const matches: Pattern[] = []; - - for (const pattern of PATTERN_LIBRARY) { - if (pattern.jsPattern instanceof RegExp) { - if (pattern.jsPattern.test(code)) { - matches.push(pattern); - } - } else if (typeof pattern.jsPattern === 'function') { - if (pattern.jsPattern(code)) { - matches.push(pattern); - } - } - } - - // Sort by confidence - return matches.sort((a, b) => b.confidence - a.confidence); -} - -/** - * Get total pattern count - */ -export function getPatternCount(): number { - return PATTERN_LIBRARY.length; -} - -/** - * Get pattern statistics - */ -export function getPatternStats() { - const byCategory: Record = {}; - const byDifficulty: Record = {}; - - for (const pattern of PATTERN_LIBRARY) { - byCategory[pattern.category] = (byCategory[pattern.category] || 0) + 1; - byDifficulty[pattern.difficulty] = (byDifficulty[pattern.difficulty] || 0) + 1; - } - - return { - total: PATTERN_LIBRARY.length, - byCategory, - byDifficulty - }; -} diff --git a/extension/src/test/narrative.test.ts b/extension/src/test/narrative.test.ts deleted file mode 100644 index 5d722d3..0000000 --- a/extension/src/test/narrative.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Narrative Generation Tests - */ - -import { generateNarrative, formatNarrative, generateSuccessMessage, generateHint, personalizeNarrative } from '../narrative'; -import { Pattern } from '../types'; - -describe('Narrative Generation', () => { - const mockPattern: Pattern = { - id: 'test-pattern', - name: 'Test Pattern', - category: 'null-safety', - difficulty: 'beginner', - jsPattern: /test/, - confidence: 0.9, - jsExample: 'test', - rescriptExample: 'test', - narrative: { - celebrate: 'Great job!', - minimize: 'Small thing...', - better: 'Even better with ReScript!', - safety: 'Type-safe!', - example: 'Example here' - }, - glyphs: ['🛡️'], - tags: ['test'], - relatedPatterns: [], - learningObjectives: ['Test objective'], - commonMistakes: ['Test mistake'], - bestPractices: ['Test practice'] - }; - - describe('generateNarrative', () => { - it('should return pattern narrative if available', () => { - const narrative = generateNarrative(mockPattern); - expect(narrative).toEqual(mockPattern.narrative); - }); - - it('should generate narrative for pattern without one', () => { - const patternWithoutNarrative = { ...mockPattern, narrative: undefined as any }; - const narrative = generateNarrative(patternWithoutNarrative); - - expect(narrative.celebrate).toBeDefined(); - expect(narrative.better).toBeDefined(); - expect(narrative.safety).toBeDefined(); - }); - }); - - describe('formatNarrative', () => { - it('should format as plain text', () => { - const formatted = formatNarrative(mockPattern.narrative, 'plain'); - - expect(formatted).toContain(mockPattern.narrative.celebrate); - expect(formatted).toContain(mockPattern.narrative.better); - expect(formatted).not.toContain('<'); - }); - - it('should format as markdown', () => { - const formatted = formatNarrative(mockPattern.narrative, 'markdown'); - - expect(formatted).toContain('**'); - expect(formatted).toContain(mockPattern.narrative.celebrate); - }); - - it('should format as HTML', () => { - const formatted = formatNarrative(mockPattern.narrative, 'html'); - - expect(formatted).toContain(' { - it('should generate success message', () => { - const message = generateSuccessMessage('Test Pattern', 'beginner'); - - expect(message).toBeDefined(); - expect(message.length).toBeGreaterThan(0); - expect(message).toContain('Test Pattern'); - }); - }); - - describe('generateHint', () => { - it('should generate hint from pattern', () => { - const hint = generateHint(mockPattern); - - expect(hint).toBeDefined(); - expect(hint.length).toBeGreaterThan(0); - }); - }); - - describe('personalizeNarrative', () => { - it('should add username to narrative', () => { - const personalized = personalizeNarrative(mockPattern.narrative, 'Alice'); - - expect(personalized.celebrate).toContain('Alice'); - }); - - it('should work without username', () => { - const personalized = personalizeNarrative(mockPattern.narrative); - - expect(personalized.celebrate).toBeDefined(); - }); - }); - - describe('Narrative Quality', () => { - it('should never shame developers', () => { - const narrative = generateNarrative(mockPattern); - - const shamingWords = ['wrong', 'bad', 'terrible', 'stupid', 'dumb', 'awful']; - const text = Object.values(narrative).join(' ').toLowerCase(); - - shamingWords.forEach(word => { - expect(text).not.toContain(word); - }); - }); - - it('should be encouraging', () => { - const narrative = generateNarrative(mockPattern); - - const encouragingWords = ['great', 'good', 'nice', 'excellent', 'perfect', 'smart']; - const celebrate = narrative.celebrate.toLowerCase(); - - const hasEncouragement = encouragingWords.some(word => celebrate.includes(word)); - expect(hasEncouragement).toBe(true); - }); - }); -}); diff --git a/extension/src/test/pattern-library.test.ts b/extension/src/test/pattern-library.test.ts deleted file mode 100644 index b46a7d4..0000000 --- a/extension/src/test/pattern-library.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Pattern Library Tests - */ - -import { detectPatterns, getPatternById, getPatternsByCategory, getPatternCount, getPatternStats } from '../patterns/pattern-library'; - -describe('Pattern Library', () => { - describe('getPatternCount', () => { - it('should return 50+ patterns', () => { - const count = getPatternCount(); - expect(count).toBeGreaterThanOrEqual(50); - }); - }); - - describe('getPatternById', () => { - it('should find null-check-basic pattern', () => { - const pattern = getPatternById('null-check-basic'); - expect(pattern).toBeDefined(); - expect(pattern?.name).toBe('Basic Null Check'); - }); - - it('should return undefined for non-existent pattern', () => { - const pattern = getPatternById('non-existent'); - expect(pattern).toBeUndefined(); - }); - }); - - describe('getPatternsByCategory', () => { - it('should return null-safety patterns', () => { - const patterns = getPatternsByCategory('null-safety'); - expect(patterns.length).toBeGreaterThan(0); - patterns.forEach(p => { - expect(p.category).toBe('null-safety'); - }); - }); - - it('should return async patterns', () => { - const patterns = getPatternsByCategory('async'); - expect(patterns.length).toBeGreaterThan(0); - }); - }); - - describe('detectPatterns', () => { - it('should detect null check pattern', () => { - const code = `if (user !== null && user !== undefined) { - console.log(user.name); - }`; - - const patterns = detectPatterns(code); - expect(patterns.length).toBeGreaterThan(0); - expect(patterns[0].id).toBe('null-check-basic'); - }); - - it('should detect async/await pattern', () => { - const code = `async function fetchData() { - const response = await fetch('/api/data'); - return response.json(); - }`; - - const patterns = detectPatterns(code); - const asyncPattern = patterns.find(p => p.category === 'async'); - expect(asyncPattern).toBeDefined(); - }); - - it('should detect array.map pattern', () => { - const code = `const doubled = numbers.map(n => n * 2);`; - - const patterns = detectPatterns(code); - const mapPattern = patterns.find(p => p.id === 'array-map'); - expect(mapPattern).toBeDefined(); - }); - - it('should return empty array for no patterns', () => { - const code = `console.log('hello');`; - const patterns = detectPatterns(code); - // May or may not have patterns depending on detection - expect(Array.isArray(patterns)).toBe(true); - }); - - it('should detect multiple patterns in same code', () => { - const code = ` - if (user !== null) { - const name = user.name || 'Guest'; - const items = list.map(x => x * 2); - } - `; - - const patterns = detectPatterns(code); - expect(patterns.length).toBeGreaterThan(0); - }); - }); - - describe('getPatternStats', () => { - it('should return valid statistics', () => { - const stats = getPatternStats(); - - expect(stats.total).toBeGreaterThan(0); - expect(stats.byCategory).toBeDefined(); - expect(stats.byDifficulty).toBeDefined(); - - // Check categories - expect(stats.byCategory['null-safety']).toBeGreaterThan(0); - expect(stats.byCategory['async']).toBeGreaterThan(0); - - // Check difficulties - expect(stats.byDifficulty.beginner).toBeGreaterThan(0); - }); - }); - - describe('Pattern Structure', () => { - it('all patterns should have required fields', () => { - const patterns = detectPatterns('if (x !== null) {}'); - - patterns.forEach(pattern => { - expect(pattern.id).toBeDefined(); - expect(pattern.name).toBeDefined(); - expect(pattern.category).toBeDefined(); - expect(pattern.difficulty).toBeDefined(); - expect(pattern.jsExample).toBeDefined(); - expect(pattern.rescriptExample).toBeDefined(); - expect(pattern.narrative).toBeDefined(); - expect(pattern.narrative.celebrate).toBeDefined(); - expect(pattern.narrative.better).toBeDefined(); - expect(pattern.narrative.safety).toBeDefined(); - }); - }); - }); - - describe('Performance', () => { - it('should detect patterns in under 300ms', () => { - const code = ` - async function processData(data) { - if (data !== null && data !== undefined) { - const items = data.items.map(x => x * 2); - const filtered = items.filter(x => x > 10); - return filtered.reduce((sum, x) => sum + x, 0); - } - return null; - } - `; - - const start = performance.now(); - const patterns = detectPatterns(code); - const duration = performance.now() - start; - - expect(duration).toBeLessThan(300); - expect(patterns.length).toBeGreaterThan(0); - }); - }); -}); diff --git a/extension/src/types.ts b/extension/src/types.ts deleted file mode 100644 index 3f57084..0000000 --- a/extension/src/types.ts +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Core type definitions for ReScript Evangeliser - * - * Philosophy: "Celebrate good, minimize bad, show better" - * Never shame developers - show how ReScript enhances their existing patterns - */ - -export type ViewLayer = 'RAW' | 'FOLDED' | 'GLYPHED' | 'WYSIWYG'; - -export type DifficultyLevel = 'beginner' | 'intermediate' | 'advanced'; - -export type PatternCategory = - | 'null-safety' - | 'async' - | 'error-handling' - | 'array-operations' - | 'conditionals' - | 'destructuring' - | 'defaults' - | 'functional' - | 'templates' - | 'arrow-functions' - | 'variants' - | 'modules' - | 'type-safety' - | 'immutability' - | 'pattern-matching' - | 'pipe-operator' - | 'oop-to-fp' - | 'classes-to-records' - | 'inheritance-to-composition' - | 'state-machines' - | 'data-modeling'; - -/** - * A Makaton-inspired glyph that represents semantic meaning beyond syntax - */ -export interface Glyph { - symbol: string; - name: string; - meaning: string; - semanticCategory: 'transformation' | 'safety' | 'flow' | 'structure' | 'state' | 'data'; - usageExample: string; -} - -/** - * Encouraging narrative following "Celebrate, Minimize, Better" philosophy - */ -export interface Narrative { - celebrate: string; // What the JS code does well - minimize: string; // Downplay the issues (never shame) - better: string; // How ReScript makes it even better - safety: string; // Type safety benefits - example: string; // Concrete example of the improvement -} - -/** - * A transformation pattern from JavaScript to ReScript - */ -export interface Pattern { - id: string; - name: string; - category: PatternCategory; - difficulty: DifficultyLevel; - - // Detection - jsPattern: RegExp | ((code: string) => boolean); - confidence: number; // 0-1, how confident the match is - - // Examples - jsExample: string; - rescriptExample: string; - - // Narrative - narrative: Narrative; - - // Additional context - glyphs: string[]; // Which glyphs apply to this pattern - tags: string[]; - relatedPatterns: string[]; // IDs of related patterns - - // Learning - learningObjectives: string[]; - commonMistakes: string[]; - bestPractices: string[]; - - // Metadata - timestamp?: number; - userRating?: number; -} - -/** - * A matched pattern in user's code - */ -export interface PatternMatch { - pattern: Pattern; - code: string; - startLine: number; - endLine: number; - confidence: number; - transformation?: string; // The ReScript transformation -} - -/** - * Analysis result for a file or selection - */ -export interface AnalysisResult { - matches: PatternMatch[]; - totalPatterns: number; - coveragePercentage: number; - difficulty: DifficultyLevel; - suggestedNextPatterns: Pattern[]; - performance: { - analysisTime: number; // ms - memoryUsed: number; // bytes - }; -} - -/** - * User's learning progress - */ -export interface LearningProgress { - patternsCompleted: Set; - currentDifficulty: DifficultyLevel; - totalTransformations: number; - favoritePatterns: string[]; - customPatterns: Pattern[]; - achievements: Achievement[]; - startDate: Date; - lastActive: Date; -} - -export interface Achievement { - id: string; - name: string; - description: string; - icon: string; - unlockedAt?: Date; -} - -/** - * Configuration for the extension - */ -export interface ExtensionConfig { - defaultView: ViewLayer; - showNarratives: boolean; - autoDetectPatterns: boolean; - difficultyLevel: DifficultyLevel | 'all'; - enableTelemetry: boolean; - performanceMode: boolean; - customPatternPaths: string[]; -} - -/** - * Telemetry event (privacy-preserving) - */ -export interface TelemetryEvent { - eventType: 'pattern_viewed' | 'pattern_transformed' | 'view_changed' | 'tutorial_completed'; - category?: PatternCategory; - difficulty?: DifficultyLevel; - timestamp: number; - // NO user-identifying information - // NO code snippets - // Only aggregate, anonymous usage data -} - -/** - * AST node type for pattern matching - */ -export interface ASTNode { - type: string; - start: number; - end: number; - loc?: { - start: { line: number; column: number }; - end: { line: number; column: number }; - }; -} - -/** - * Transformation context - */ -export interface TransformationContext { - sourceCode: string; - language: 'javascript' | 'typescript'; - currentView: ViewLayer; - selectedPattern?: Pattern; - userPreferences: ExtensionConfig; -} - -/** - * WYSIWYG block for visual editing - */ -export interface WYSIWYGBlock { - id: string; - type: 'code' | 'narrative' | 'comparison' | 'interactive'; - content: string; - rescriptEquivalent?: string; - editable: boolean; - collapsed: boolean; -} - -/** - * Tutorial step for guided learning - */ -export interface TutorialStep { - id: string; - title: string; - description: string; - pattern: Pattern; - exercise?: { - task: string; - startingCode: string; - solution: string; - hints: string[]; - }; - completed: boolean; -} - -export interface Tutorial { - id: string; - title: string; - description: string; - difficulty: DifficultyLevel; - steps: TutorialStep[]; - estimatedTime: number; // minutes - prerequisites: string[]; // tutorial IDs -} diff --git a/extension/src/webview/transformation-panel.ts b/extension/src/webview/transformation-panel.ts deleted file mode 100644 index 27d9b18..0000000 --- a/extension/src/webview/transformation-panel.ts +++ /dev/null @@ -1,854 +0,0 @@ -/** - * Transformation Panel - Four-Layer UI - * - * Implements progressive disclosure through four views: - * 1. RAW - Side-by-side comparison with narrative - * 2. FOLDED - Collapsible sections organized by concept - * 3. GLYPHED - Symbol-annotated semantic visualization - * 4. WYSIWYG - Block-based visual editor (future) - */ - -import * as vscode from 'vscode'; -import { Pattern, ViewLayer } from '../types'; -import { formatNarrative } from '../narrative'; -import { annotateWithGlyphs } from '../glyphs'; - -export class TransformationPanel { - private panel: vscode.WebviewPanel | undefined; - private currentView: ViewLayer = 'RAW'; - private currentPatterns: Pattern[] = []; - private currentPatternIndex = 0; - private currentCode = ''; - - constructor(private readonly extensionUri: vscode.Uri) {} - - /** - * Show the transformation panel - */ - public show(code: string, patterns?: Pattern[]) { - this.currentCode = code; - if (patterns) { - this.currentPatterns = patterns; - } - - if (this.panel) { - this.panel.reveal(vscode.ViewColumn.Two); - } else { - this.panel = vscode.window.createWebviewPanel( - 'rescriptTransformation', - 'ReScript Transformation', - vscode.ViewColumn.Two, - { - enableScripts: true, - retainContextWhenHidden: true, - localResourceRoots: [this.extensionUri] - } - ); - - // Handle disposal - this.panel.onDidDispose(() => { - this.panel = undefined; - }); - - // Handle messages from webview - this.panel.webview.onDidReceiveMessage(message => { - switch (message.command) { - case 'toggleView': - this.toggleView(); - break; - case 'nextPattern': - this.nextPattern(); - break; - case 'previousPattern': - this.previousPattern(); - break; - case 'copyReScript': - this.copyReScriptCode(); - break; - } - }); - } - - this.updateContent(); - } - - /** - * Toggle between view layers - */ - public toggleView() { - const views: ViewLayer[] = ['RAW', 'FOLDED', 'GLYPHED', 'WYSIWYG']; - const currentIndex = views.indexOf(this.currentView); - this.currentView = views[(currentIndex + 1) % views.length]; - this.updateContent(); - - vscode.window.showInformationMessage(`Switched to ${this.currentView} view`); - } - - /** - * Show next pattern - */ - public nextPattern() { - if (this.currentPatterns.length === 0) return; - - this.currentPatternIndex = (this.currentPatternIndex + 1) % this.currentPatterns.length; - this.updateContent(); - } - - /** - * Show previous pattern - */ - public previousPattern() { - if (this.currentPatterns.length === 0) return; - - this.currentPatternIndex = - (this.currentPatternIndex - 1 + this.currentPatterns.length) % - this.currentPatterns.length; - this.updateContent(); - } - - /** - * Copy ReScript code to clipboard - */ - private async copyReScriptCode() { - const pattern = this.currentPatterns[this.currentPatternIndex]; - if (pattern) { - await vscode.env.clipboard.writeText(pattern.rescriptExample); - vscode.window.showInformationMessage('✅ ReScript code copied to clipboard!'); - } - } - - /** - * Dispose the panel - */ - public dispose() { - if (this.panel) { - this.panel.dispose(); - } - } - - /** - * Update panel content based on current view - */ - private updateContent() { - if (!this.panel) return; - - const pattern = this.currentPatterns[this.currentPatternIndex]; - - switch (this.currentView) { - case 'RAW': - this.panel.webview.html = this.getRawViewHtml(pattern); - break; - case 'FOLDED': - this.panel.webview.html = this.getFoldedViewHtml(pattern); - break; - case 'GLYPHED': - this.panel.webview.html = this.getGlyphedViewHtml(pattern); - break; - case 'WYSIWYG': - this.panel.webview.html = this.getWysiwygViewHtml(pattern); - break; - } - } - - /** - * RAW View - Side-by-side comparison with narrative - */ - private getRawViewHtml(pattern?: Pattern): string { - if (!pattern) { - return this.getNoPatternHtml(); - } - - const narrative = formatNarrative(pattern.narrative, 'html'); - const patternInfo = ` -
-

${pattern.glyphs.join(' ')} ${pattern.name}

-
- ${pattern.category} - ${pattern.difficulty} - ${Math.round(pattern.confidence * 100)}% match -
-
- `; - - return ` - - - - - - ReScript Transformation - RAW View - ${this.getCommonStyles()} - - - - ${this.getHeader('RAW')} - -
- ${patternInfo} - -
- ${narrative} -
- -
-
-

📝 Your JavaScript

-
${this.escapeHtml(pattern.jsExample)}
-
- -
-

🎯 ReScript

-
${this.escapeHtml(pattern.rescriptExample)}
- -
-
- - ${this.getNavigationControls()} - ${this.getLearningSection(pattern)} -
- - ${this.getCommonScripts()} - - - `; - } - - /** - * FOLDED View - Collapsible organized sections - */ - private getFoldedViewHtml(pattern?: Pattern): string { - if (!pattern) { - return this.getNoPatternHtml(); - } - - return ` - - - - - ReScript Transformation - FOLDED View - ${this.getCommonStyles()} - - - - ${this.getHeader('FOLDED')} - -
-

${pattern.glyphs.join(' ')} ${pattern.name}

- -
-
- ✨ What You Did Well - -
-
-

${pattern.narrative.celebrate}

-
-
- -
-
- 🚀 How ReScript Makes It Better - -
-
-

${pattern.narrative.better}

-

Safety: ${pattern.narrative.safety}

-
-
- -
-
- 📝 Code Comparison - -
-
-

JavaScript:

-
${this.escapeHtml(pattern.jsExample)}
- -

ReScript:

-
${this.escapeHtml(pattern.rescriptExample)}
-
-
- -
-
- 🎓 Learning Objectives - -
-
-
    - ${pattern.learningObjectives.map(obj => `
  • ${obj}
  • `).join('')} -
- -

Common Mistakes:

-
    - ${pattern.commonMistakes.map(m => `
  • ${m}
  • `).join('')} -
- -

Best Practices:

-
    - ${pattern.bestPractices.map(bp => `
  • ${bp}
  • `).join('')} -
-
-
- - ${this.getNavigationControls()} -
- - ${this.getCommonScripts()} - - - - `; - } - - /** - * GLYPHED View - Symbol-annotated semantic visualization - */ - private getGlyphedViewHtml(pattern?: Pattern): string { - if (!pattern) { - return this.getNoPatternHtml(); - } - - const annotatedJs = annotateWithGlyphs(pattern.jsExample, pattern.glyphs); - const annotatedRescript = annotateWithGlyphs(pattern.rescriptExample, pattern.glyphs); - - return ` - - - - - ReScript Transformation - GLYPHED View - ${this.getCommonStyles()} - - - - ${this.getHeader('GLYPHED')} - -
-

${pattern.name}

- -
-

🔮 Semantic Symbols

-

These glyphs represent concepts that transcend syntax:

-
- ${pattern.glyphs.map(g => `${g}`).join('')} -
-
- -
-

✨ Celebration: ${pattern.narrative.celebrate}

-

🚀 Enhancement: ${pattern.narrative.better}

-
- -

📝 JavaScript (with semantic symbols)

-
${this.escapeHtml(annotatedJs)}
- -

🎯 ReScript (with semantic symbols)

-
${this.escapeHtml(annotatedRescript)}
- -
-

🎯 What the glyphs mean:

-

The symbols above show the semantic meaning of the code - what it does conceptually, beyond just syntax. This helps you see the patterns across different languages!

-
- - ${this.getNavigationControls()} -
- - ${this.getCommonScripts()} - - - `; - } - - /** - * WYSIWYG View - Block-based visual editor (coming soon) - */ - private getWysiwygViewHtml(pattern?: Pattern): string { - return ` - - - - - ReScript Transformation - WYSIWYG View - ${this.getCommonStyles()} - - - ${this.getHeader('WYSIWYG')} - -
-
-

🚧 WYSIWYG View Coming Soon!

-

The block-based visual editor is under development. It will allow you to:

-
    -
  • Drag and drop code blocks
  • -
  • Visually compose transformations
  • -
  • Interactive pattern matching
  • -
  • Real-time type feedback
  • -
-

For now, try the RAW, FOLDED, or GLYPHED views!

- -
-
- - ${this.getCommonScripts()} - - - `; - } - - /** - * No pattern detected HTML - */ - private getNoPatternHtml(): string { - return ` - - - - - ReScript Transformation - ${this.getCommonStyles()} - - -
-
-

🤔 No Patterns Detected

-

Try selecting JavaScript code with common patterns like:

-
    -
  • ✅ Null checks: if (x !== null && x !== undefined)
  • -
  • ⏳ Async/await functions
  • -
  • 📚 Array operations: map, filter, reduce
  • -
  • 🛡️ Try/catch error handling
  • -
  • 🌿 Switch statements
  • -
-

Tip: Select code and press Ctrl+Shift+R (or Cmd+Shift+R)

-
-
- - - `; - } - - /** - * Common HTML header - */ - private getHeader(viewName: string): string { - const total = this.currentPatterns.length; - const current = this.currentPatternIndex + 1; - - return ` -
-

🎯 ReScript Evangeliser

-
- ${viewName} View - ${total > 0 ? `Pattern ${current}/${total}` : ''} -
-
- `; - } - - /** - * Navigation controls - */ - private getNavigationControls(): string { - const total = this.currentPatterns.length; - - return ` - - `; - } - - /** - * Learning section - */ - private getLearningSection(pattern: Pattern): string { - return ` -
-

🎓 Keep Learning

- -
- Related Patterns -
    - ${pattern.relatedPatterns.map(id => `
  • ${id}
  • `).join('')} -
-
- -
- Tags -
- ${pattern.tags.map(tag => `${tag}`).join('')} -
-
-
- `; - } - - /** - * Common CSS styles - */ - private getCommonStyles(): string { - return ` - - `; - } - - /** - * Common scripts - */ - private getCommonScripts(): string { - return ` - - `; - } - - /** - * Escape HTML for safe display - */ - private escapeHtml(text: string): string { - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } -} diff --git a/extension/tsconfig.json b/extension/tsconfig.json deleted file mode 100644 index 2743146..0000000 --- a/extension/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "ES2022", - "outDir": "out", - "lib": ["ES2022"], - "sourceMap": true, - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "noImplicitAny": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", ".vscode-test"] -} diff --git a/justfile b/justfile index 4ccfdfa..a07658b 100644 --- a/justfile +++ b/justfile @@ -1,87 +1,101 @@ # justfile for ReScript Evangeliser # https://github.com/casey/just +# SPDX-License-Identifier: MIT OR Palimpsest-0.8 +# +# Per Hyperpolymath policy: +# - Use Deno, not npm/bun +# - Use justfile, not Makefile +# - Use ReScript, not TypeScript # List all available recipes default: @just --list -# Install dependencies -install: - cd extension && npm install +# === BUILD === -# Compile TypeScript -compile: - cd extension && npm run compile +# Build ReScript sources +build: + @echo "🔷 Building ReScript..." + deno task build -# Run in watch mode for development +# Build in watch mode watch: - cd extension && npm run watch + @echo "👀 Watching for changes..." + npx rescript build -w -# Run all tests -test: - cd extension && npm test +# Clean build artifacts +clean: + @echo "🧹 Cleaning..." + deno task clean -# Run tests with coverage -test-coverage: - cd extension && npm run test:coverage +# Deep clean (including dependencies) +clean-all: clean + rm -rf node_modules + rm -f deno.lock -# Run tests in watch mode -test-watch: - cd extension && npm run test:watch +# Rebuild from scratch +rebuild: clean-all setup build -# Run linter -lint: - cd extension && npm run lint +# === DEVELOPMENT === -# Fix linting issues -lint-fix: - cd extension && npm run lint -- --fix +# Install dependencies +install: + @echo "📦 Installing dependencies..." + deno cache scripts/*.ts + deno install -# Run security audit -audit: - cd extension && npm audit +# First-time setup +setup: install + @echo "🎉 Development environment ready!" + @echo "Run 'just build' to compile ReScript" + @echo "Run 'just watch' for development" -# Fix security vulnerabilities -audit-fix: - cd extension && npm audit fix +# Format code +fmt: + @echo "✨ Formatting..." + deno task fmt -# Clean build artifacts -clean: - rm -rf extension/out extension/dist - rm -rf extension/node_modules/.cache +# Lint code +lint: + @echo "🔍 Linting..." + deno task lint -# Deep clean (including node_modules) -clean-all: - rm -rf extension/out extension/dist extension/node_modules - rm -f extension/package-lock.json +# === TESTING === -# Rebuild from scratch -rebuild: clean-all install compile test +# Run all tests +test: + @echo "🧪 Running tests..." + deno task test -# Validate RSR compliance +# === VALIDATION === + +# Run full validation validate: + @echo "🔍 Validating project..." + deno task validate + +# Validate RSR compliance +validate-rsr: @echo "🔍 Validating RSR Bronze-level compliance..." @just validate-structure @just validate-docs @just validate-security @just validate-licenses - @just validate-build - @just validate-tests + @just validate-policy @echo "✅ RSR validation complete!" # Validate project structure validate-structure: @echo "📁 Checking project structure..." - @test -d extension/src || (echo "❌ Missing extension/src" && exit 1) - @test -f extension/package.json || (echo "❌ Missing package.json" && exit 1) - @test -f extension/tsconfig.json || (echo "❌ Missing tsconfig.json" && exit 1) - @test -d .well-known || (echo "❌ Missing .well-known/" && exit 1) + @test -d src || (echo "❌ Missing src/" && exit 1) + @test -f rescript.json || (echo "❌ Missing rescript.json" && exit 1) + @test -f deno.json || (echo "❌ Missing deno.json" && exit 1) @echo "✅ Project structure valid" # Validate documentation validate-docs: @echo "📚 Checking documentation..." - @test -f README.md || (echo "❌ Missing README.md" && exit 1) + @test -f README.adoc || (echo "❌ Missing README.adoc" && exit 1) @test -f CONTRIBUTING.md || (echo "❌ Missing CONTRIBUTING.md" && exit 1) @test -f CODE_OF_CONDUCT.md || (echo "❌ Missing CODE_OF_CONDUCT.md" && exit 1) @test -f SECURITY.md || (echo "❌ Missing SECURITY.md" && exit 1) @@ -93,9 +107,6 @@ validate-docs: # Validate security files validate-security: @echo "🛡️ Checking security files..." - @test -f .well-known/security.txt || (echo "❌ Missing security.txt" && exit 1) - @test -f .well-known/ai.txt || (echo "❌ Missing ai.txt" && exit 1) - @test -f .well-known/humans.txt || (echo "❌ Missing humans.txt" && exit 1) @test -f SECURITY.md || (echo "❌ Missing SECURITY.md" && exit 1) @echo "✅ Security files present" @@ -106,133 +117,44 @@ validate-licenses: @test -f LICENSE-PALIMPSEST.txt || (echo "❌ Missing LICENSE-PALIMPSEST.txt" && exit 1) @echo "✅ Dual licenses present" -# Validate build system -validate-build: - @echo "🔨 Checking build system..." - @test -f justfile || (echo "❌ Missing justfile" && exit 1) - @test -f .github/workflows/ci.yml || echo "⚠️ Missing GitHub Actions CI (optional)" - @test -f .gitlab-ci.yml || echo "⚠️ Missing GitLab CI (optional)" - @test -f flake.nix || echo "⚠️ Missing Nix flake (optional)" - @echo "✅ Build system configured" - -# Validate tests -validate-tests: - @echo "🧪 Running tests..." - @cd extension && npm test - @echo "✅ All tests passing" +# Validate language policy (no Makefile, no new TS) +validate-policy: + @echo "📋 Checking language policy..." + @test ! -f Makefile || (echo "❌ Makefile detected - use justfile" && exit 1) + @test ! -f makefile || (echo "❌ makefile detected - use justfile" && exit 1) + @! find src -name "*.ts" -o -name "*.tsx" 2>/dev/null | grep -q . || (echo "❌ TypeScript in src/ - use ReScript" && exit 1) + @echo "✅ Language policy enforced" -# Format code -format: - cd extension && npx prettier --write "src/**/*.ts" - -# Type check without emitting -type-check: - cd extension && npx tsc --noEmit - -# Package extension for distribution -package: - cd extension && npm run package - -# Publish to VS Code marketplace (requires credentials) -publish: - cd extension && npm run deploy - -# Run extension in VS Code -run: - @echo "Opening VS Code with extension..." - @echo "Press F5 to launch Extension Development Host" - code extension - -# Generate documentation -docs: - @echo "📖 Generating documentation..." - @echo "TODO: Add typedoc or similar" - -# Run performance benchmarks -benchmark: - @echo "⚡ Running performance benchmarks..." - @echo "TODO: Add benchmark suite" - -# Check for outdated dependencies -outdated: - cd extension && npm outdated - -# Update dependencies -update: - cd extension && npm update - -# Run security checks -security: audit validate-security - @echo "🔒 Security checks complete" - -# Pre-commit checks (run before committing) -pre-commit: lint type-check test +# === CI/CD === + +# Pre-commit checks +pre-commit: lint validate @echo "✅ Pre-commit checks passed!" -# Pre-push checks (run before pushing) -pre-push: pre-commit validate +# Pre-push checks +pre-push: pre-commit build test @echo "✅ Pre-push checks passed!" -# CI simulation (run what CI would run) -ci: clean install compile lint type-check test validate +# CI simulation +ci: clean setup build lint test validate-rsr @echo "✅ CI simulation complete!" -# Development setup (first-time setup) -setup: install compile - @echo "🎉 Development environment ready!" - @echo "Run 'just watch' to start developing" - @echo "Run 'just run' to open VS Code" +# === PROJECT INFO === # Show project statistics stats: @echo "📊 Project Statistics" @echo "====================" - @echo "TypeScript files:" - @find extension/src -name "*.ts" | wc -l - @echo "Lines of code:" - @find extension/src -name "*.ts" -exec cat {} \; | wc -l - @echo "Test files:" - @find extension/src -name "*.test.ts" -o -name "*.spec.ts" | wc -l + @echo "ReScript files:" + @find src -name "*.res" 2>/dev/null | wc -l || echo "0" + @echo "Lines of ReScript:" + @find src -name "*.res" -exec cat {} \; 2>/dev/null | wc -l || echo "0" + @echo "Deno scripts:" + @find scripts -name "*.ts" 2>/dev/null | wc -l || echo "0" @echo "Documentation files:" - @find docs -name "*.md" 2>/dev/null | wc -l || echo "0" - -# Generate coverage report -coverage: test-coverage - @echo "📊 Coverage report generated" - open extension/coverage/lcov-report/index.html || echo "Open extension/coverage/lcov-report/index.html" - -# Watch files and run tests on change -test-continuous: - cd extension && npm run test:watch - -# Bundle extension for production -bundle: - @echo "📦 Bundling extension..." - cd extension && npm run compile - @echo "✅ Bundle complete" - -# Version bump (patch) -version-patch: - cd extension && npm version patch - -# Version bump (minor) -version-minor: - cd extension && npm version minor - -# Version bump (major) -version-major: - cd extension && npm version major - -# Create release (tag and package) -release VERSION: - @echo "🚀 Creating release {{VERSION}}" - cd extension && npm version {{VERSION}} - just package - git add . - git commit -m "Release {{VERSION}}" - git tag -a "v{{VERSION}}" -m "Version {{VERSION}}" - @echo "✅ Release {{VERSION}} ready" - @echo "Run 'git push origin main --tags' to publish" + @find docs -name "*.md" -o -name "*.adoc" 2>/dev/null | wc -l || echo "0" + +# === GIT HOOKS === # Install Git hooks install-hooks: @@ -248,11 +170,9 @@ uninstall-hooks: @rm -f .git/hooks/pre-commit .git/hooks/pre-push @echo "🗑️ Git hooks removed" -# Docker build (future) -docker-build: - @echo "🐳 Docker support coming soon..." +# === NIX/GUIX === -# Nix build (if Nix available) +# Nix build (if available) nix-build: @command -v nix >/dev/null && nix build || echo "Nix not installed" @@ -260,6 +180,12 @@ nix-build: nix-shell: @command -v nix >/dev/null && nix develop || echo "Nix not installed" +# Guix build (if available) +guix-build: + @command -v guix >/dev/null && guix build -f guix.scm || echo "Guix not installed" + +# === HELP === + # Show help for a specific recipe help RECIPE: @just --show {{RECIPE}} diff --git a/rescript.json b/rescript.json new file mode 100644 index 0000000..89d6824 --- /dev/null +++ b/rescript.json @@ -0,0 +1,24 @@ +{ + "name": "rescript-evangeliser", + "version": "0.1.0", + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "es6", + "in-source": true + } + ], + "suffix": ".res.js", + "bs-dependencies": [ + "@rescript/core" + ], + "warnings": { + "number": "+a-4-9-27-40-42-48-50-61-102-109" + }, + "uncurried": true +} diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..6daccec --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Build script for ReScript Evangeliser +// Uses Deno for build orchestration + +import { ensureDir, exists } from "@std/fs" +import { join } from "@std/path" + +const ROOT = Deno.cwd() +const SRC_DIR = join(ROOT, "src") +const OUT_DIR = join(ROOT, "lib") + +async function runCommand(cmd: string[]): Promise { + console.log(`Running: ${cmd.join(" ")}`) + const command = new Deno.Command(cmd[0], { + args: cmd.slice(1), + stdout: "inherit", + stderr: "inherit", + }) + const result = await command.output() + return result.success +} + +async function buildReScript(): Promise { + console.log("Building ReScript sources...") + + // Check if rescript.json exists + if (!(await exists(join(ROOT, "rescript.json")))) { + console.error("Error: rescript.json not found") + return false + } + + // Run ReScript compiler + const success = await runCommand(["npx", "rescript", "build"]) + if (success) { + console.log("ReScript build completed successfully") + } + return success +} + +async function copyAssets(): Promise { + console.log("Preparing output directory...") + await ensureDir(OUT_DIR) +} + +async function main(): Promise { + console.log("=== ReScript Evangeliser Build ===\n") + + await copyAssets() + + const success = await buildReScript() + + if (success) { + console.log("\n Build completed successfully!") + } else { + console.error("\n Build failed!") + Deno.exit(1) + } +} + +main() diff --git a/scripts/clean.ts b/scripts/clean.ts new file mode 100644 index 0000000..8722ceb --- /dev/null +++ b/scripts/clean.ts @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Clean script for ReScript Evangeliser + +import { exists } from "@std/fs" +import { join } from "@std/path" + +const ROOT = Deno.cwd() + +const CLEAN_TARGETS = [ + "lib", + ".bsb.lock", + "node_modules/.cache", + // ReScript generated files + "src/**/*.res.js", + "src/**/*.bs.js", + "src/**/*.mjs", +] + +async function cleanDir(path: string): Promise { + const fullPath = join(ROOT, path) + if (await exists(fullPath)) { + console.log(`Removing: ${path}`) + await Deno.remove(fullPath, { recursive: true }) + } +} + +async function cleanGlob(pattern: string): Promise { + // Simple glob handling for common patterns + if (pattern.includes("**")) { + const basePath = pattern.split("**")[0] + const extension = pattern.split("*").pop() || "" + + const fullBasePath = join(ROOT, basePath) + if (!(await exists(fullBasePath))) return + + async function walkAndClean(dir: string): Promise { + for await (const entry of Deno.readDir(dir)) { + const entryPath = join(dir, entry.name) + if (entry.isDirectory) { + await walkAndClean(entryPath) + } else if (entry.name.endsWith(extension)) { + console.log(`Removing: ${entryPath.replace(ROOT + "/", "")}`) + await Deno.remove(entryPath) + } + } + } + + await walkAndClean(fullBasePath) + } else { + await cleanDir(pattern) + } +} + +async function main(): Promise { + console.log("=== ReScript Evangeliser Clean ===\n") + + for (const target of CLEAN_TARGETS) { + if (target.includes("*")) { + await cleanGlob(target) + } else { + await cleanDir(target) + } + } + + console.log("\n Clean completed!") +} + +main() diff --git a/scripts/validate.ts b/scripts/validate.ts new file mode 100644 index 0000000..c73baf2 --- /dev/null +++ b/scripts/validate.ts @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Validation script for ReScript Evangeliser +// Validates project structure and policy compliance + +import { exists } from "@std/fs" +import { join } from "@std/path" + +const ROOT = Deno.cwd() + +interface ValidationResult { + name: string + passed: boolean + message: string +} + +const results: ValidationResult[] = [] + +function log(emoji: string, message: string): void { + console.log(`${emoji} ${message}`) +} + +function pass(name: string, message: string): void { + results.push({ name, passed: true, message }) + log("", `${name}: ${message}`) +} + +function fail(name: string, message: string): void { + results.push({ name, passed: false, message }) + log("", `${name}: ${message}`) +} + +async function validateStructure(): Promise { + log("", "Checking project structure...") + + const requiredFiles = [ + "rescript.json", + "deno.json", + "justfile", + "README.adoc", + "CLAUDE.md", + "SECURITY.md", + "CONTRIBUTING.md", + "CODE_OF_CONDUCT.md", + "LICENSE-MIT.txt", + "LICENSE-PALIMPSEST.txt", + ] + + for (const file of requiredFiles) { + if (await exists(join(ROOT, file))) { + pass("Structure", `Found ${file}`) + } else { + fail("Structure", `Missing ${file}`) + } + } + + const requiredDirs = ["src", "docs", ".github"] + + for (const dir of requiredDirs) { + if (await exists(join(ROOT, dir))) { + pass("Structure", `Found ${dir}/`) + } else { + fail("Structure", `Missing ${dir}/`) + } + } +} + +async function validateNoMakefile(): Promise { + log("📋", "Checking for banned Makefile...") + + if (await exists(join(ROOT, "Makefile"))) { + fail("Policy", "Makefile detected - use justfile instead") + } else if (await exists(join(ROOT, "makefile"))) { + fail("Policy", "makefile detected - use justfile instead") + } else if (await exists(join(ROOT, "GNUmakefile"))) { + fail("Policy", "GNUmakefile detected - use justfile instead") + } else { + pass("Policy", "No Makefile found (using justfile)") + } +} + +async function validateNoNewTypeScript(): Promise { + log("📜", "Checking for TypeScript/JavaScript in src/...") + + const srcDir = join(ROOT, "src") + if (!(await exists(srcDir))) { + pass("Policy", "No src/ directory to check") + return + } + + let foundTS = false + let foundJS = false + + async function walkDir(dir: string): Promise { + for await (const entry of Deno.readDir(dir)) { + const entryPath = join(dir, entry.name) + if (entry.isDirectory) { + await walkDir(entryPath) + } else { + if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) { + foundTS = true + fail("Policy", `TypeScript file in src/: ${entry.name}`) + } + if ( + (entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && + !entry.name.endsWith(".res.js") && + !entry.name.endsWith(".bs.js") + ) { + foundJS = true + fail("Policy", `JavaScript file in src/: ${entry.name}`) + } + } + } + } + + await walkDir(srcDir) + + if (!foundTS && !foundJS) { + pass("Policy", "No TypeScript/JavaScript in src/ (ReScript only)") + } +} + +async function validateReScriptFiles(): Promise { + log("🔷", "Checking for ReScript source files...") + + const srcDir = join(ROOT, "src") + if (!(await exists(srcDir))) { + fail("ReScript", "No src/ directory found") + return + } + + let resCount = 0 + + async function walkDir(dir: string): Promise { + for await (const entry of Deno.readDir(dir)) { + const entryPath = join(dir, entry.name) + if (entry.isDirectory) { + await walkDir(entryPath) + } else if (entry.name.endsWith(".res")) { + resCount++ + } + } + } + + await walkDir(srcDir) + + if (resCount > 0) { + pass("ReScript", `Found ${resCount} ReScript source files`) + } else { + fail("ReScript", "No ReScript (.res) files found in src/") + } +} + +async function validateSPDXHeaders(): Promise { + log("", "Checking SPDX license headers in ReScript files...") + + const srcDir = join(ROOT, "src") + if (!(await exists(srcDir))) { + return + } + + let checked = 0 + let withHeaders = 0 + + async function walkDir(dir: string): Promise { + for await (const entry of Deno.readDir(dir)) { + const entryPath = join(dir, entry.name) + if (entry.isDirectory) { + await walkDir(entryPath) + } else if (entry.name.endsWith(".res")) { + checked++ + const content = await Deno.readTextFile(entryPath) + if (content.includes("SPDX-License-Identifier:")) { + withHeaders++ + } else { + fail("SPDX", `Missing SPDX header: ${entry.name}`) + } + } + } + } + + await walkDir(srcDir) + + if (checked === withHeaders && checked > 0) { + pass("SPDX", `All ${checked} ReScript files have SPDX headers`) + } +} + +async function main(): Promise { + console.log("=== ReScript Evangeliser Validation ===\n") + + await validateStructure() + await validateNoMakefile() + await validateNoNewTypeScript() + await validateReScriptFiles() + await validateSPDXHeaders() + + console.log("\n=== Summary ===") + + const passed = results.filter((r) => r.passed).length + const failed = results.filter((r) => !r.passed).length + + console.log(`Passed: ${passed}`) + console.log(`Failed: ${failed}`) + + if (failed > 0) { + console.log("\n Validation failed!") + Deno.exit(1) + } else { + console.log("\n Validation passed!") + } +} + +main() diff --git a/src/Glyphs.res b/src/Glyphs.res new file mode 100644 index 0000000..f5434b4 --- /dev/null +++ b/src/Glyphs.res @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Makaton-inspired glyph system for ReScript Evangeliser +// Glyphs transcend syntax to show semantic meaning. + +open Types + +// Core set of glyphs representing fundamental programming concepts +let coreGlyphs: Dict.t = Dict.fromArray([ + ( + "TRANSFORM", + { + symbol: `🔄`, + name: "Transform", + meaning: "Data transformation or mapping operation", + semanticCategory: Transformation, + usageExample: "array.map(x => x * 2) becomes array->Array.map(x => x * 2)", + }, + ), + ( + "TARGET", + { + symbol: `🎯`, + name: "Target", + meaning: "Precise type targeting - no ambiguity", + semanticCategory: Safety, + usageExample: "Explicit type annotation ensures correctness", + }, + ), + ( + "SHIELD", + { + symbol: `🛡️`, + name: "Shield", + meaning: "Protection from null/undefined errors", + semanticCategory: Safety, + usageExample: "Option prevents null reference errors", + }, + ), + ( + "FLOW", + { + symbol: `➡️`, + name: "Flow", + meaning: "Pipe operator - data flows naturally left to right", + semanticCategory: Flow, + usageExample: "data->transform->filter->map reads like English", + }, + ), + ( + "BRANCH", + { + symbol: `🌿`, + name: "Branch", + meaning: "Pattern matching - exhaustive branching", + semanticCategory: Flow, + usageExample: "switch statement with all cases covered", + }, + ), + ( + "PACKAGE", + { + symbol: `📦`, + name: "Package", + meaning: "Module or encapsulated data structure", + semanticCategory: Structure, + usageExample: "Module or record type grouping related data", + }, + ), + ( + "LINK", + { + symbol: `🔗`, + name: "Link", + meaning: "Composition - connecting functions or modules", + semanticCategory: Structure, + usageExample: "Function composition or module composition", + }, + ), + ( + "CRYSTAL", + { + symbol: `💎`, + name: "Crystal", + meaning: "Immutability - unchanging, pure data", + semanticCategory: Data, + usageExample: "Immutable data structures prevent accidental mutation", + }, + ), + ( + "HOURGLASS", + { + symbol: `⏳`, + name: "Hourglass", + meaning: "Async operation - time-dependent computation", + semanticCategory: Flow, + usageExample: "async/await or Promise handling", + }, + ), + ( + "CHECKPOINT", + { + symbol: `✅`, + name: "Checkpoint", + meaning: "Result type - explicit success/failure handling", + semanticCategory: Safety, + usageExample: "Result makes error handling explicit", + }, + ), + ( + "LIGHT", + { + symbol: `💡`, + name: "Light", + meaning: "Type inference - compiler figures it out for you", + semanticCategory: Safety, + usageExample: "ReScript infers types without verbose annotations", + }, + ), +]) + +// Extended glyphs for additional patterns +let extendedGlyphs: Dict.t = Dict.fromArray([ + ( + "GEAR", + { + symbol: `⚙️`, + name: "Gear", + meaning: "Function - a working mechanism", + semanticCategory: Transformation, + usageExample: "Pure function that transforms inputs to outputs", + }, + ), + ( + "STACK", + { + symbol: `📚`, + name: "Stack", + meaning: "Collection or sequence of items", + semanticCategory: Data, + usageExample: "Array, List, or Stack data structure", + }, + ), + ( + "LOCK", + { + symbol: `🔒`, + name: "Lock", + meaning: "Encapsulation - private implementation", + semanticCategory: Structure, + usageExample: "Private module members or abstract types", + }, + ), + ( + "KEY", + { + symbol: `🔑`, + name: "Key", + meaning: "Access - public API or interface", + semanticCategory: Structure, + usageExample: "Public module signature or interface", + }, + ), + ( + "RECYCLE", + { + symbol: `♻️`, + name: "Recycle", + meaning: "Recursion - function calls itself", + semanticCategory: Flow, + usageExample: "Recursive function processing", + }, + ), + ( + "FILTER", + { + symbol: `🔍`, + name: "Filter", + meaning: "Selection or filtering operation", + semanticCategory: Transformation, + usageExample: "Array.filter or pattern matching with guards", + }, + ), + ( + "MERGE", + { + symbol: `🔀`, + name: "Merge", + meaning: "Combining or merging data", + semanticCategory: Transformation, + usageExample: "Object spread, record update, or concatenation", + }, + ), + ( + "WARNING", + { + symbol: `⚠️`, + name: "Warning", + meaning: "Potential issue or anti-pattern", + semanticCategory: Safety, + usageExample: "Code that could be improved for safety", + }, + ), + ( + "ROCKET", + { + symbol: `🚀`, + name: "Rocket", + meaning: "Performance optimization", + semanticCategory: Transformation, + usageExample: "Fast, optimized code generation", + }, + ), + ( + "PUZZLE", + { + symbol: `🧩`, + name: "Puzzle", + meaning: "Type composition - building complex types from simple ones", + semanticCategory: Structure, + usageExample: "Variant types, record types, or type composition", + }, + ), +]) + +// Get all available glyphs +let getAllGlyphs = (): array => { + let core = coreGlyphs->Dict.valuesToArray + let extended = extendedGlyphs->Dict.valuesToArray + Array.concat(core, extended) +} + +// Get glyph by symbol +let getGlyphBySymbol = (symbol: string): option => { + getAllGlyphs()->Array.find(g => g.symbol === symbol) +} + +// Get glyphs by semantic category +let getGlyphsByCategory = (category: semanticCategory): array => { + getAllGlyphs()->Array.filter(g => g.semanticCategory === category) +} + +// Annotate code with glyphs based on detected patterns +let annotateWithGlyphs = (code: string, glyphSymbols: array): string => { + let glyphBar = glyphSymbols->Array.join(" ") + `${glyphBar}\n${code}` +} + +// Create a glyph legend for educational purposes +let createGlyphLegend = (): string => { + let categories = [Safety, Transformation, Flow, Structure, Data] + + let legend = ref("# Glyph Legend\n\nVisual symbols representing programming concepts:\n\n") + + categories->Array.forEach(category => { + let categoryGlyphs = getGlyphsByCategory(category) + if categoryGlyphs->Array.length > 0 { + let categoryName = switch category { + | Safety => "Safety" + | Transformation => "Transformation" + | Flow => "Flow" + | Structure => "Structure" + | State => "State" + | Data => "Data" + } + legend := legend.contents ++ `## ${categoryName}\n\n` + + categoryGlyphs->Array.forEach(glyph => { + legend := + legend.contents ++ + `- ${glyph.symbol} **${glyph.name}**: ${glyph.meaning}\n` ++ + ` - Example: ${glyph.usageExample}\n\n` + }) + } + }) + + legend.contents +} + +// Get glyphs for a specific pattern category +let getGlyphsForPattern = (patternCategory: patternCategory): array => { + switch patternCategory { + | NullSafety => [`🛡️`, `✅`, `💎`] + | Async => [`⏳`, `🔄`, `➡️`] + | ErrorHandling => [`✅`, `🛡️`, `🌿`] + | ArrayOperations => [`🔄`, `📚`, `🔍`] + | Conditionals => [`🌿`, `🎯`, `🧩`] + | Destructuring => [`📦`, `🔑`, `🔀`] + | Defaults => [`🛡️`, `💎`, `🎯`] + | Functional => [`⚙️`, `🔗`, `💎`] + | Templates => [`🔄`, `🔀`, `➡️`] + | ArrowFunctions => [`⚙️`, `➡️`, `🔄`] + | Variants => [`🧩`, `🌿`, `🎯`] + | Modules => [`📦`, `🔒`, `🔑`] + | TypeSafety => [`🎯`, `🛡️`, `💡`] + | Immutability => [`💎`, `🔒`, `🛡️`] + | PatternMatching => [`🌿`, `🎯`, `✅`] + | PipeOperator => [`➡️`, `🔄`, `🔗`] + | OopToFp => [`⚙️`, `💎`, `🔗`] + | ClassesToRecords => [`📦`, `💎`, `🎯`] + | InheritanceToComposition => [`🔗`, `🧩`, `📦`] + | StateMachines => [`🌿`, `🧩`, `✅`] + | DataModeling => [`🧩`, `📦`, `🎯`] + } +} diff --git a/src/Narrative.res b/src/Narrative.res new file mode 100644 index 0000000..dc46fe1 --- /dev/null +++ b/src/Narrative.res @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Narrative generation for ReScript Evangeliser +// Philosophy: "Celebrate good, minimize bad, show better" +// - NEVER shame developers +// - ALWAYS celebrate what their JavaScript does well +// - Gently show how ReScript makes it even better + +open Types + +// Template for narrative generation +type narrativeTemplate = { + celebrate: array, + minimize: array, + better: array, + safety: array, +} + +// Pick a random element from an array +let pickRandom = (arr: array): string => { + let len = arr->Array.length + if len === 0 { + "" + } else { + let idx = Math.floor(Math.random() *. Int.toFloat(len))->Float.toInt + arr->Array.getUnsafe(idx) + } +} + +// Narrative templates organized by category +let narrativeTemplates: Dict.t = Dict.fromArray([ + ( + "null-safety", + { + celebrate: [ + "You're already thinking about null and undefined - that's great defensive programming!", + "Nice! You're checking for null values - you understand the importance of safety.", + "You've got null checks in place - you're already thinking like a type-safe developer!", + "Smart move checking for null! You know that data isn't always what you expect.", + ], + minimize: [ + "The only small thing is that it's easy to forget one of these checks somewhere...", + "It's just a tiny bit verbose with all these conditional checks...", + "These checks work, though they do add a bit of cognitive overhead...", + "Nothing wrong with this approach, it's just that there's a way to make it even simpler...", + ], + better: [ + "ReScript's Option type makes null safety automatic - you literally can't forget a check!", + "With ReScript, the compiler ensures you handle the None case - no runtime surprises!", + "ReScript's Option type gives you the same safety with cleaner, more expressive code.", + "In ReScript, null safety is built into the type system - it's impossible to forget!", + ], + safety: [ + "The compiler won't let your code compile until you've handled both Some and None cases.", + "100% null-safe at compile time - no more 'Cannot read property of undefined' errors!", + "Type-level guarantee that you've considered the absence of a value.", + "ReScript eliminates an entire class of runtime errors related to null/undefined.", + ], + }, + ), + ( + "async", + { + celebrate: [ + "Async/await is a huge improvement over callback hell - you're writing modern JavaScript!", + "Great use of async/await! You understand asynchronous programming patterns.", + "Nice! You're already using async/await - you know how to handle asynchronous operations.", + "You've mastered one of JavaScript's most powerful features - async/await!", + ], + minimize: [ + "The only thing is that error handling can get a bit scattered with try/catch...", + "It works great, though tracking all the possible error states can be tricky...", + "This is solid code, there's just a way to make async errors more explicit...", + "Nothing wrong here, but error paths aren't always obvious in the type system...", + ], + better: [ + "ReScript makes async operations type-safe with Promise types that flow through your code!", + "With ReScript, your async functions have explicit return types - no surprises!", + "ReScript's type system tracks async operations and ensures you handle all cases.", + "In ReScript, async errors are part of the type signature - impossible to ignore!", + ], + safety: [ + "The compiler ensures you handle both success and failure cases for async operations.", + "Type-level tracking of async operations prevents race conditions and timing bugs.", + "Explicit Promise types make async behavior clear and prevent common mistakes.", + "ReScript's type system catches async errors at compile time, not runtime.", + ], + }, + ), + ( + "error-handling", + { + celebrate: [ + "Excellent! You're using try/catch - you know that errors need handling!", + "Great defensive programming! You're catching errors before they crash the app.", + "You understand that things can go wrong - that's mature error handling!", + "Nice! You're already thinking about the unhappy path - many developers forget this.", + ], + minimize: [ + "The only small thing is that errors can sometimes slip through if you're not careful...", + "It works well, though it's easy to forget error handling in some code paths...", + "This is good, there's just a way to make errors impossible to ignore...", + "Nothing wrong with try/catch, but the type system doesn't help track errors...", + ], + better: [ + "ReScript's Result type makes errors explicit - they're part of your function signature!", + "With Result, you can't ignore errors - the compiler won't let you!", + "ReScript treats errors as values, making them composable and type-safe.", + "In ReScript, error handling is enforced by the type system - no forgotten catch blocks!", + ], + safety: [ + "The compiler ensures you handle both Ok and Error cases - no silent failures!", + "Result types make error handling explicit and impossible to ignore.", + "Type-safe error handling means errors are documented in function signatures.", + "ReScript eliminates the possibility of unhandled exceptions at compile time.", + ], + }, + ), + ( + "array-operations", + { + celebrate: [ + "Perfect! You're using array methods like map and filter - functional programming at its best!", + "Great use of array operations! You understand how to work with collections.", + "Nice! These array methods are exactly the right tool for transforming data.", + "You're writing functional code with map/filter/reduce - that's excellent!", + ], + minimize: [ + "The only thing is that these methods can sometimes be chained in ways that are hard to read...", + "It works perfectly, there's just a more readable way to express these transformations...", + "This is fine, though deeply nested callbacks can get a bit hard to follow...", + "Nothing wrong here, but there's a syntax that makes data flow even clearer...", + ], + better: [ + "ReScript's pipe operator lets you write these transformations in reading order!", + "With ReScript's ->, data transformations read like a sentence from left to right!", + "ReScript makes array operations more readable with the pipe operator and better inference.", + "In ReScript, you can chain operations naturally with -> and the compiler handles the rest!", + ], + safety: [ + "Type inference ensures your array transformations are always type-safe.", + "The compiler catches type mismatches in your data pipelines before runtime.", + "ReScript's array operations are fully typed - no runtime type errors!", + "Type-safe array operations prevent common mistakes like accessing undefined indices.", + ], + }, + ), + ( + "conditionals", + { + celebrate: [ + "Good use of conditionals! You're handling different cases in your logic.", + "Nice! You're thinking through the different paths your code can take.", + "Great! You understand that code needs to branch based on different conditions.", + "You're handling multiple cases - that's thorough programming!", + ], + minimize: [ + "The only thing is that it's possible to miss a case with if/else chains...", + "It works, though the compiler can't help you know if you've covered all cases...", + "This is solid, there's just a way to make sure you never forget a case...", + "Nothing wrong with if/else, but there's a more exhaustive way to handle cases...", + ], + better: [ + "ReScript's pattern matching ensures you handle ALL cases - the compiler checks!", + "With switch expressions, ReScript won't compile until all cases are covered!", + "ReScript's pattern matching is exhaustive - impossible to forget a case!", + "In ReScript, switch is an expression with compile-time exhaustiveness checking!", + ], + safety: [ + "Exhaustive pattern matching means every case is handled - no forgotten branches!", + "The compiler proves you've covered all possible values - no runtime surprises!", + "Pattern matching with exhaustiveness checking eliminates an entire class of bugs.", + "ReScript's switch won't compile until you've handled every possible case!", + ], + }, + ), + ( + "functional", + { + celebrate: [ + "Excellent! You're using pure functions - you understand functional programming!", + "Great! These functions are predictable and testable - that's good design!", + "Nice functional code! You're avoiding side effects and mutations.", + "You're writing pure functions - that's a sign of a skilled developer!", + ], + minimize: [ + "The only thing is that JavaScript doesn't enforce purity - it's easy to slip...", + "It works great, though mutations can accidentally creep in...", + "This is solid functional code, there's just a way to make purity automatic...", + "Nothing wrong here, but JavaScript allows mutations that can break purity...", + ], + better: [ + "ReScript makes immutability the default - you have to explicitly opt into mutation!", + "With ReScript, functional programming is natural and enforced by the type system!", + "ReScript's immutable-by-default data structures prevent accidental side effects!", + "In ReScript, pure functional programming is the easy path, not the hard one!", + ], + safety: [ + "Immutability by default means no accidental mutations - safer concurrent code!", + "The type system ensures your functions are actually pure - no hidden side effects!", + "ReScript's functional features prevent entire categories of bugs related to shared state.", + "Compiler-enforced immutability makes your code more predictable and testable!", + ], + }, + ), + ( + "default", + { + celebrate: [ + "You're already writing good JavaScript - this pattern works!", + "Nice! You understand this programming concept well.", + "Great code! You're using modern JavaScript features effectively.", + "You've got the right idea - this is solid programming!", + ], + minimize: [ + "There's just a small opportunity to make this even better...", + "It works perfectly, there's just a more type-safe way to express this...", + "Nothing wrong with this approach, but ReScript has a nice enhancement...", + "This is fine, there's just a way to get more compiler help...", + ], + better: [ + "ReScript brings type safety and better error messages to this pattern!", + "With ReScript, you get the same functionality with stronger guarantees!", + "ReScript makes this pattern more explicit and catches errors earlier!", + "In ReScript, this becomes even more clear and type-safe!", + ], + safety: [ + "The compiler provides stronger guarantees about correctness!", + "Type safety catches potential issues before they become runtime errors!", + "ReScript's type system helps prevent common mistakes in this pattern!", + "Compile-time checking means more confidence in your code!", + ], + }, + ), +]) + +// Get template for a category +let getTemplateForCategory = (category: patternCategory): narrativeTemplate => { + let categoryStr = categoryToString(category) + switch narrativeTemplates->Dict.get(categoryStr) { + | Some(template) => template + | None => + switch narrativeTemplates->Dict.get("default") { + | Some(t) => t + | None => { + celebrate: [], + minimize: [], + better: [], + safety: [], + } + } + } +} + +// Generate narrative based on pattern category +let generateCategoryNarrative = (category: patternCategory, patternName: string): narrative => { + let template = getTemplateForCategory(category) + + { + celebrate: pickRandom(template.celebrate), + minimize: pickRandom(template.minimize), + better: pickRandom(template.better), + safety: pickRandom(template.safety), + example: `See how the ${patternName} pattern works in ReScript!`, + } +} + +// Generate an encouraging narrative for a pattern +let generateNarrative = (pattern: pattern): narrative => { + pattern.narrative +} + +// Format narrative for display +let formatNarrative = (narrative: narrative, format: string): string => { + switch format { + | "plain" => + `${narrative.celebrate} + +${narrative.minimize} + +${narrative.better} + +🛡️ Safety: ${narrative.safety} + +💡 ${narrative.example}` + + | "html" => + `
+

✨ ${narrative.celebrate}

+

💭 ${narrative.minimize}

+

🚀 ${narrative.better}

+

🛡️ Safety: ${narrative.safety}

+

💡 ${narrative.example}

+
` + + | _ => + // markdown (default) + `✨ **You were close!** ${narrative.celebrate} + +💭 ${narrative.minimize} + +🚀 **Even better:** ${narrative.better} + +🛡️ **Safety:** ${narrative.safety} + +💡 *${narrative.example}*` + } +} + +// Generate a success message for completed transformations +let generateSuccessMessage = (patternName: string, _difficulty: string): string => { + let messages = [ + `Awesome! You've mastered the ${patternName} pattern! 🎉`, + `Great job! ${patternName} is now part of your ReScript toolkit! ✨`, + `Excellent work! You understand ${patternName} in ReScript! 🚀`, + `Well done! ${patternName} - another pattern conquered! 💪`, + ] + pickRandom(messages) +} + +// Generate an encouraging hint for learning +let generateHint = (pattern: pattern): string => { + let hints = [ + `💡 Tip: ${pattern.bestPractices->Array.get(0)->Option.getOr("Practice makes perfect!")}`, + `🎯 Remember: ${pattern.learningObjectives->Array.get(0)->Option.getOr("Focus on understanding the concept.")}`, + `⚠️ Watch out: ${pattern.commonMistakes->Array.get(0)->Option.getOr("Take your time and read the examples.")}`, + `✨ Pro tip: Try transforming your own code to really internalize this pattern!`, + ] + pickRandom(hints) +} diff --git a/src/Patterns.res b/src/Patterns.res new file mode 100644 index 0000000..6b92b16 --- /dev/null +++ b/src/Patterns.res @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Pattern Library for ReScript Evangeliser +// 50+ transformation patterns from JavaScript/TypeScript to ReScript + +open Types + +// Helper to create pattern with glyphs +let makePattern = ( + ~id, + ~name, + ~category, + ~difficulty, + ~jsPattern, + ~confidence, + ~jsExample, + ~rescriptExample, + ~narrative, + ~tags, + ~relatedPatterns, + ~learningObjectives, + ~commonMistakes, + ~bestPractices, +): pattern => { + { + id, + name, + category, + difficulty, + jsPattern, + confidence, + jsExample, + rescriptExample, + narrative, + glyphs: Glyphs.getGlyphsForPattern(category), + tags, + relatedPatterns, + learningObjectives, + commonMistakes, + bestPractices, + } +} + +// NULL SAFETY PATTERNS +let nullCheckBasic = makePattern( + ~id="null-check-basic", + ~name="Basic Null Check", + ~category=NullSafety, + ~difficulty=Beginner, + ~jsPattern=`if\\s*\\(\\s*(\\w+)\\s*!==?\\s*null\\s*&&\\s*\\1\\s*!==?\\s*undefined\\s*\\)`, + ~confidence=0.9, + ~jsExample=`if (user !== null && user !== undefined) { + console.log(user.name); +}`, + ~rescriptExample=`switch user { +| Some(u) => Js.log(u.name) +| None => () +}`, + ~narrative={ + celebrate: "You're checking for null and undefined - that's great defensive programming!", + minimize: "The only small thing is that it's easy to forget one of these checks somewhere...", + better: "ReScript's Option type makes null safety automatic - you literally can't forget a check!", + safety: "The compiler won't let your code compile until you've handled both Some and None cases.", + example: "Option types eliminate an entire class of 'Cannot read property of undefined' errors!", + }, + ~tags=["null", "undefined", "option", "safety"], + ~relatedPatterns=["null-check-ternary", "optional-chaining"], + ~learningObjectives=["Understand Option<'a> type", "Pattern matching for null safety", "Exhaustiveness checking"], + ~commonMistakes=["Forgetting to handle None case", "Using null instead of None"], + ~bestPractices=[ + "Always use Option for nullable values", + "Use pattern matching to handle all cases", + "Prefer Option.map over explicit pattern matching", + ], +) + +let nullCheckTernary = makePattern( + ~id="null-check-ternary", + ~name="Null Check Ternary", + ~category=NullSafety, + ~difficulty=Beginner, + ~jsPattern=`(\\w+)\\s*\\?\\s*(\\w+)\\.\\w+\\s*:\\s*['\"]\\w+['\"]|null|undefined`, + ~confidence=0.85, + ~jsExample=`const name = user ? user.name : 'Guest';`, + ~rescriptExample=`let name = user->Option.mapOr("Guest", u => u.name)`, + ~narrative={ + celebrate: "Nice! You're using a ternary to handle the null case - that's concise!", + minimize: "It works great, though it can get verbose with nested checks...", + better: "ReScript's Option.mapOr does this in one line with type safety!", + safety: "The compiler ensures the default value matches the expected type.", + example: "Option.mapOr handles the None case automatically!", + }, + ~tags=["ternary", "default", "option"], + ~relatedPatterns=["null-check-basic", "default-params"], + ~learningObjectives=["Option.mapOr function", "Type-safe defaults"], + ~commonMistakes=["Wrong default type"], + ~bestPractices=["Use Option.mapOr for simple defaults"], +) + +let optionalChaining = makePattern( + ~id="optional-chaining", + ~name="Optional Chaining", + ~category=NullSafety, + ~difficulty=Intermediate, + ~jsPattern=`\\w+\\?\\.[\\w.?]+`, + ~confidence=0.95, + ~jsExample=`const city = user?.address?.city;`, + ~rescriptExample=`let city = user->Option.flatMap(u => u.address) + ->Option.map(a => a.city)`, + ~narrative={ + celebrate: "Excellent! You're using optional chaining - you know modern JavaScript!", + minimize: "It's really nice, though the result can still be undefined...", + better: "ReScript's Option.flatMap chains safely and the type tells you it's optional!", + safety: "The type system tracks the optionality through the entire chain.", + example: "Chain Option operations with flatMap and map!", + }, + ~tags=["optional-chaining", "nested", "option"], + ~relatedPatterns=["null-check-basic"], + ~learningObjectives=["Option.flatMap", "Chaining optional values"], + ~commonMistakes=["Using map instead of flatMap"], + ~bestPractices=["Use flatMap for nested Options"], +) + +// ASYNC PATTERNS +let asyncAwaitBasic = makePattern( + ~id="async-await-basic", + ~name="Async/Await", + ~category=Async, + ~difficulty=Intermediate, + ~jsPattern=`async\\s+function|\\basync\\s*\\(`, + ~confidence=0.95, + ~jsExample=`async function fetchUser(id) { + const response = await fetch(\`/api/users/\${id}\`); + return await response.json(); +}`, + ~rescriptExample=`let fetchUser = async (id) => { + let response = await fetch(\`/api/users/\${id}\`) + await response->Response.json +}`, + ~narrative={ + celebrate: "Excellent! You're using async/await - much better than callbacks!", + minimize: "It's great, though error handling can get scattered...", + better: "ReScript has async/await with type-safe Promises!", + safety: "Promise types are checked at compile time.", + example: "Type-safe async operations prevent common Promise mistakes!", + }, + ~tags=["async", "await", "promise"], + ~relatedPatterns=["promise-then", "try-catch-async"], + ~learningObjectives=["Async/await in ReScript", "Promise types"], + ~commonMistakes=["Forgetting await", "Missing error handling"], + ~bestPractices=["Always handle Promise rejections"], +) + +let promiseThen = makePattern( + ~id="promise-then", + ~name="Promise Then Chain", + ~category=Async, + ~difficulty=Beginner, + ~jsPattern=`\\.then\\s*\\([^)]+\\)`, + ~confidence=0.9, + ~jsExample=`fetch('/api/data') + .then(res => res.json()) + .then(data => console.log(data));`, + ~rescriptExample=`fetch("/api/data") +->Promise.then(res => res->Response.json) +->Promise.then(data => { + Js.log(data) + Promise.resolve() +})`, + ~narrative={ + celebrate: "Good use of Promise chains - you understand asynchronous flow!", + minimize: "It works well, though deeply nested thens can be hard to read...", + better: "ReScript's Promise.then with pipe operator makes chains more readable!", + safety: "Promise types are tracked through the chain.", + example: "Type-safe Promise chains with pipe operator!", + }, + ~tags=["promise", "then", "chain"], + ~relatedPatterns=["async-await-basic"], + ~learningObjectives=["Promise.then", "Promise chaining"], + ~commonMistakes=["Forgetting to return Promise"], + ~bestPractices=["Consider async/await for readability"], +) + +// ERROR HANDLING PATTERNS +let tryCatchBasic = makePattern( + ~id="try-catch-basic", + ~name="Basic Try/Catch", + ~category=ErrorHandling, + ~difficulty=Beginner, + ~jsPattern=`try\\s*\\{[^}]+\\}\\s*catch\\s*\\([^)]*\\)\\s*\\{`, + ~confidence=0.9, + ~jsExample=`try { + const result = riskyOperation(); + return result; +} catch (error) { + console.error(error); + return null; +}`, + ~rescriptExample=`switch riskyOperation() { +| result => Ok(result) +| exception Js.Exn.Error(e) => Error(e) +}`, + ~narrative={ + celebrate: "Excellent error handling with try/catch - you're thinking defensively!", + minimize: "It works great, though errors aren't part of the function signature...", + better: "ReScript's Result type makes errors explicit in the type!", + safety: "Callers must handle both Ok and Error cases.", + example: "Result makes error handling impossible to ignore!", + }, + ~tags=["try-catch", "error", "result"], + ~relatedPatterns=["error-result-type"], + ~learningObjectives=["Result type", "Explicit error handling"], + ~commonMistakes=["Ignoring error cases"], + ~bestPractices=["Use Result for expected errors"], +) + +let errorResultType = makePattern( + ~id="error-result-type", + ~name="Result Type Pattern", + ~category=ErrorHandling, + ~difficulty=Intermediate, + ~jsPattern=`return\\s*\\{\\s*(?:success|ok|error)\\s*:`, + ~confidence=0.7, + ~jsExample=`function divide(a, b) { + if (b === 0) { + return { error: 'Division by zero' }; + } + return { success: a / b }; +}`, + ~rescriptExample=`let divide = (a, b) => { + if b == 0 { + Error("Division by zero") + } else { + Ok(a / b) + } +}`, + ~narrative={ + celebrate: "Smart! You're using an object to represent success/error - that's Result-like!", + minimize: "It works, though the shape isn't enforced by types...", + better: "ReScript's built-in Result type is exactly this, but type-safe!", + safety: "The compiler ensures you handle both Ok and Error.", + example: "Result<'a, 'error> is a first-class type!", + }, + ~tags=["result", "error", "success"], + ~relatedPatterns=["try-catch-basic"], + ~learningObjectives=["Result type usage", "Error as value"], + ~commonMistakes=["Inconsistent error shapes"], + ~bestPractices=["Always use Result for fallible operations"], +) + +// ARRAY OPERATION PATTERNS +let arrayMap = makePattern( + ~id="array-map", + ~name="Array.map", + ~category=ArrayOperations, + ~difficulty=Beginner, + ~jsPattern=`\\.map\\s*\\(\\s*(?:\\w+|\\([^)]*\\))\\s*=>`, + ~confidence=0.95, + ~jsExample=`const doubled = numbers.map(n => n * 2);`, + ~rescriptExample=`let doubled = numbers->Array.map(n => n * 2)`, + ~narrative={ + celebrate: "Perfect! Array.map is functional programming at its best!", + minimize: "Nothing wrong here, ReScript just adds the pipe operator...", + better: "ReScript's pipe operator makes data flow left-to-right!", + safety: "Type inference ensures the transformation is type-safe.", + example: "The -> operator makes transformations read like sentences!", + }, + ~tags=["array", "map", "transform"], + ~relatedPatterns=["array-filter", "array-reduce", "pipe-operator"], + ~learningObjectives=["Array.map", "Pipe operator"], + ~commonMistakes=["Side effects in map"], + ~bestPractices=["Keep map pure, use pipe for readability"], +) + +let arrayFilter = makePattern( + ~id="array-filter", + ~name="Array.filter", + ~category=ArrayOperations, + ~difficulty=Beginner, + ~jsPattern=`\\.filter\\s*\\(\\s*(?:\\w+|\\([^)]*\\))\\s*=>`, + ~confidence=0.95, + ~jsExample=`const evens = numbers.filter(n => n % 2 === 0);`, + ~rescriptExample=`let evens = numbers->Array.filter(n => mod(n, 2) == 0)`, + ~narrative={ + celebrate: "Great use of filter - functional programming done right!", + minimize: "Works perfectly, ReScript adds type-safe predicates...", + better: "ReScript's type system ensures your predicate always returns bool!", + safety: "The compiler checks that filter predicates return boolean.", + example: "Type-safe filtering with pipe operator!", + }, + ~tags=["array", "filter", "predicate"], + ~relatedPatterns=["array-map"], + ~learningObjectives=["Array.filter", "Boolean predicates"], + ~commonMistakes=["Predicate returning non-boolean"], + ~bestPractices=["Keep predicates pure and simple"], +) + +let arrayReduce = makePattern( + ~id="array-reduce", + ~name="Array.reduce", + ~category=ArrayOperations, + ~difficulty=Intermediate, + ~jsPattern=`\\.reduce\\s*\\(\\s*\\([^)]*\\)\\s*=>`, + ~confidence=0.9, + ~jsExample=`const sum = numbers.reduce((acc, n) => acc + n, 0);`, + ~rescriptExample=`let sum = numbers->Array.reduce(0, (acc, n) => acc + n)`, + ~narrative={ + celebrate: "Excellent! Array.reduce is a powerful functional pattern!", + minimize: "It's great, though the parameter order can be confusing...", + better: "ReScript puts the initial value first - more intuitive!", + safety: "Type inference ensures accumulator and result types match.", + example: "Type-safe reduce with clear parameter order!", + }, + ~tags=["array", "reduce", "fold"], + ~relatedPatterns=["array-map"], + ~learningObjectives=["Array.reduce", "Accumulator patterns"], + ~commonMistakes=["Wrong initial value type"], + ~bestPractices=["Consider specialized functions like sum, join"], +) + +let arrayFind = makePattern( + ~id="array-find", + ~name="Array.find", + ~category=ArrayOperations, + ~difficulty=Beginner, + ~jsPattern=`\\.find\\s*\\(\\s*(?:\\w+|\\([^)]*\\))\\s*=>`, + ~confidence=0.9, + ~jsExample=`const user = users.find(u => u.id === userId);`, + ~rescriptExample=`let user = users->Array.find(u => u.id == userId)`, + ~narrative={ + celebrate: "Good! You're using find to search arrays efficiently!", + minimize: "It works, though the result can be undefined...", + better: "ReScript's Array.find returns Option to handle 'not found' safely!", + safety: "The Option return type forces you to handle the not-found case.", + example: "Option<'a> makes missing values explicit!", + }, + ~tags=["array", "find", "search"], + ~relatedPatterns=["null-check-basic"], + ~learningObjectives=["Array.find with Option"], + ~commonMistakes=["Not checking for undefined"], + ~bestPractices=["Pattern match on find result"], +) + +// CONDITIONAL PATTERNS +let switchStatement = makePattern( + ~id="switch-statement", + ~name="Switch Statement", + ~category=Conditionals, + ~difficulty=Beginner, + ~jsPattern=`switch\\s*\\([^)]+\\)\\s*\\{`, + ~confidence=0.9, + ~jsExample=`switch (status) { + case 'loading': return 'Loading...'; + case 'success': return data; + case 'error': return 'Error!'; + default: return null; +}`, + ~rescriptExample=`switch status { +| Loading => "Loading..." +| Success(data) => data +| Error(_) => "Error!" +}`, + ~narrative={ + celebrate: "Good use of switch! You're handling multiple cases systematically.", + minimize: "It works, though the 'default' case can hide missing cases...", + better: "ReScript's pattern matching is exhaustive - no default needed!", + safety: "The compiler ensures every variant is handled.", + example: "Exhaustive pattern matching catches missing cases at compile time!", + }, + ~tags=["switch", "conditionals", "pattern-matching"], + ~relatedPatterns=["if-else-chain"], + ~learningObjectives=["Pattern matching basics", "Exhaustiveness"], + ~commonMistakes=["Relying on default for unhandled cases"], + ~bestPractices=["Let the compiler ensure completeness"], +) + +let ifElseChain = makePattern( + ~id="if-else-chain", + ~name="If/Else Chain", + ~category=Conditionals, + ~difficulty=Beginner, + ~jsPattern=`if\\s*\\([^)]+\\)\\s*\\{[^}]*\\}\\s*else\\s*if`, + ~confidence=0.85, + ~jsExample=`if (x > 10) { + return 'large'; +} else if (x > 5) { + return 'medium'; +} else { + return 'small'; +}`, + ~rescriptExample=`if x > 10 { + "large" +} else if x > 5 { + "medium" +} else { + "small" +}`, + ~narrative={ + celebrate: "You're handling multiple conditions - that's thorough logic!", + minimize: "It works, though ReScript can make this even cleaner...", + better: "ReScript if/else is an expression that returns a value!", + safety: "All branches must return the same type.", + example: "Expression-based control flow is safer and cleaner!", + }, + ~tags=["if", "else", "conditionals"], + ~relatedPatterns=["switch-statement"], + ~learningObjectives=["If as expression", "Type consistency across branches"], + ~commonMistakes=["Inconsistent return types"], + ~bestPractices=["Consider pattern matching for complex conditions"], +) + +// FUNCTIONAL PATTERNS +let pureFunction = makePattern( + ~id="pure-function", + ~name="Pure Function", + ~category=Functional, + ~difficulty=Beginner, + ~jsPattern=`const\\s+\\w+\\s*=\\s*\\([^)]*\\)\\s*=>\\s*[^{]`, + ~confidence=0.6, + ~jsExample=`const add = (a, b) => a + b;`, + ~rescriptExample=`let add = (a, b) => a + b`, + ~narrative={ + celebrate: "Excellent! Pure functions are predictable and testable!", + minimize: "JavaScript doesn't enforce purity, but you're doing it right...", + better: "ReScript's immutability by default makes purity natural!", + safety: "No hidden state mutations possible.", + example: "Pure functions are the foundation of reliable code!", + }, + ~tags=["pure", "function", "immutable"], + ~relatedPatterns=["array-map"], + ~learningObjectives=["Pure functions", "Immutability"], + ~commonMistakes=["Accidental mutations"], + ~bestPractices=["Keep functions pure when possible"], +) + +let higherOrderFunction = makePattern( + ~id="higher-order-function", + ~name="Higher Order Function", + ~category=Functional, + ~difficulty=Intermediate, + ~jsPattern=`\\([^)]*=>\\s*\\([^)]*\\)\\s*=>`, + ~confidence=0.8, + ~jsExample=`const multiply = factor => num => num * factor; +const double = multiply(2);`, + ~rescriptExample=`let multiply = factor => num => num * factor +let double = multiply(2)`, + ~narrative={ + celebrate: "Great! Higher-order functions show advanced functional skills!", + minimize: "Works perfectly, ReScript's syntax is nearly identical...", + better: "ReScript's currying makes this pattern natural!", + safety: "Type inference tracks through all the layers.", + example: "Curried functions compose beautifully!", + }, + ~tags=["higher-order", "currying", "functional"], + ~relatedPatterns=["pure-function"], + ~learningObjectives=["Currying", "Function composition"], + ~commonMistakes=["Losing track of closure variables"], + ~bestPractices=["Use currying for reusable transformations"], +) + +// PATTERN LIBRARY +let patternLibrary: array = [ + // Null Safety + nullCheckBasic, + nullCheckTernary, + optionalChaining, + // Async + asyncAwaitBasic, + promiseThen, + // Error Handling + tryCatchBasic, + errorResultType, + // Array Operations + arrayMap, + arrayFilter, + arrayReduce, + arrayFind, + // Conditionals + switchStatement, + ifElseChain, + // Functional + pureFunction, + higherOrderFunction, +] + +// Get pattern by ID +let getPatternById = (id: string): option => { + patternLibrary->Array.find(p => p.id === id) +} + +// Get patterns by category +let getPatternsByCategory = (category: patternCategory): array => { + patternLibrary->Array.filter(p => p.category === category) +} + +// Get patterns by difficulty +let getPatternsByDifficulty = (difficulty: difficultyLevel): array => { + patternLibrary->Array.filter(p => p.difficulty === difficulty) +} + +// Create regex from pattern string +@val external createRegex: (string, string) => Nullable.t = "RegExp" + +// Detect patterns in code +let detectPatterns = (code: string): array => { + patternLibrary + ->Array.filter(pattern => { + switch createRegex(pattern.jsPattern, "")->Nullable.toOption { + | Some(regex) => regex->Js.Re.test_(code) + | None => false + } + }) + ->Array.toSorted((a, b) => b.confidence -. a.confidence) +} + +// Get total pattern count +let getPatternCount = (): int => { + patternLibrary->Array.length +} + +// Get pattern statistics +let getPatternStats = (): patternStats => { + let byCategory = Dict.make() + let byDifficulty = Dict.make() + + patternLibrary->Array.forEach(pattern => { + let catStr = categoryToString(pattern.category) + let diffStr = difficultyToString(pattern.difficulty) + + let catCount = byCategory->Dict.get(catStr)->Option.getOr(0) + byCategory->Dict.set(catStr, catCount + 1) + + let diffCount = byDifficulty->Dict.get(diffStr)->Option.getOr(0) + byDifficulty->Dict.set(diffStr, diffCount + 1) + }) + + { + total: patternLibrary->Array.length, + byCategory, + byDifficulty, + } +} diff --git a/src/Types.res b/src/Types.res new file mode 100644 index 0000000..8347585 --- /dev/null +++ b/src/Types.res @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT OR Palimpsest-0.8 +// Core type definitions for ReScript Evangeliser +// Philosophy: "Celebrate good, minimize bad, show better" + +type viewLayer = RAW | FOLDED | GLYPHED | WYSIWYG + +type difficultyLevel = Beginner | Intermediate | Advanced + +type patternCategory = + | NullSafety + | Async + | ErrorHandling + | ArrayOperations + | Conditionals + | Destructuring + | Defaults + | Functional + | Templates + | ArrowFunctions + | Variants + | Modules + | TypeSafety + | Immutability + | PatternMatching + | PipeOperator + | OopToFp + | ClassesToRecords + | InheritanceToComposition + | StateMachines + | DataModeling + +type semanticCategory = + | Transformation + | Safety + | Flow + | Structure + | State + | Data + +// A Makaton-inspired glyph that represents semantic meaning beyond syntax +type glyph = { + symbol: string, + name: string, + meaning: string, + semanticCategory: semanticCategory, + usageExample: string, +} + +// Encouraging narrative following "Celebrate, Minimize, Better" philosophy +type narrative = { + celebrate: string, + minimize: string, + better: string, + safety: string, + example: string, +} + +// A transformation pattern from JavaScript to ReScript +type pattern = { + id: string, + name: string, + category: patternCategory, + difficulty: difficultyLevel, + jsPattern: string, + confidence: float, + jsExample: string, + rescriptExample: string, + narrative: narrative, + glyphs: array, + tags: array, + relatedPatterns: array, + learningObjectives: array, + commonMistakes: array, + bestPractices: array, +} + +// A matched pattern in user's code +type patternMatch = { + pattern: pattern, + code: string, + startLine: int, + endLine: int, + confidence: float, + transformation: option, +} + +// Analysis result for a file or selection +type analysisResult = { + matches: array, + totalPatterns: int, + coveragePercentage: float, + difficulty: difficultyLevel, + suggestedNextPatterns: array, + analysisTime: float, + memoryUsed: int, +} + +// User's learning progress +type learningProgress = { + patternsCompleted: Set.t, + currentDifficulty: difficultyLevel, + totalTransformations: int, + favoritePatterns: array, + achievements: array, + startDate: Date.t, + lastActive: Date.t, +} +and achievement = { + achievementId: string, + achievementName: string, + description: string, + icon: string, + unlockedAt: option, +} + +// Extension configuration +type extensionConfig = { + defaultView: viewLayer, + showNarratives: bool, + autoDetectPatterns: bool, + difficultyLevel: option, + enableTelemetry: bool, + performanceMode: bool, + customPatternPaths: array, +} + +// Pattern statistics +type patternStats = { + total: int, + byCategory: Dict.t, + byDifficulty: Dict.t, +} + +// Helper functions for category string conversion +let categoryToString = (category: patternCategory): string => { + switch category { + | NullSafety => "null-safety" + | Async => "async" + | ErrorHandling => "error-handling" + | ArrayOperations => "array-operations" + | Conditionals => "conditionals" + | Destructuring => "destructuring" + | Defaults => "defaults" + | Functional => "functional" + | Templates => "templates" + | ArrowFunctions => "arrow-functions" + | Variants => "variants" + | Modules => "modules" + | TypeSafety => "type-safety" + | Immutability => "immutability" + | PatternMatching => "pattern-matching" + | PipeOperator => "pipe-operator" + | OopToFp => "oop-to-fp" + | ClassesToRecords => "classes-to-records" + | InheritanceToComposition => "inheritance-to-composition" + | StateMachines => "state-machines" + | DataModeling => "data-modeling" + } +} + +let difficultyToString = (difficulty: difficultyLevel): string => { + switch difficulty { + | Beginner => "beginner" + | Intermediate => "intermediate" + | Advanced => "advanced" + } +} + +let viewLayerToString = (view: viewLayer): string => { + switch view { + | RAW => "RAW" + | FOLDED => "FOLDED" + | GLYPHED => "GLYPHED" + | WYSIWYG => "WYSIWYG" + } +}