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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Copilot Instructions for SuperOffice Language Tools

## Repository Overview

VS Code extensions for TypeScript and CRMScript in SuperOffice. Implements LSP servers using Volar (TypeScript) and Langium (CRMScript).

**Type:** Monorepo (pnpm workspace) | **Languages:** TypeScript | **Frameworks:** Volar, Langium, VS Code Extension API | **Size:** ~521 TS files | **Runtime:** Node.js 20.x | **Package Manager:** pnpm 8.x (REQUIRED)

## Build & Test Requirements

### Prerequisites
- **Node.js:** 20.x | **pnpm:** 8.x (install: `npm install -g pnpm@8`)
- **Critical:** ONLY pnpm works - npm/yarn will fail due to workspace config and preinstall hooks

### Installation
```bash
pnpm install # ALWAYS run first
```

### Build Process

**Build order matters!** Some packages depend on outputs from others:

1. **Build langium-crmscript FIRST** (generates TS from .langium grammars):
```bash
pnpm run build:langium-crmscript # Outputs to src/language/generated/ & syntaxes/
```

2. **Build VS Code extensions:**
```bash
pnpm run build:vscode-core # Core extension (esbuild → dist/)
pnpm run build:vscode-tsfso # TS extension (esbuild → dist/)
pnpm run build:vscode-crmscript # CRMScript extension (esbuild → out/*.cjs)
```

3. **CI Build Sequence:**
```bash
pnpm install && pnpm run build:vscode-core && pnpm run build:vscode-tsfso && pnpm test && pnpm eslint .
```

### Testing

**Vitest** (7 tests, ~400ms):
```bash
pnpm test # All tests
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage
```

**VS Code Extension Tests** (SKIP in CI/sandboxed environments - requires internet):
```bash
pnpm run test:core # Fails offline: "getaddrinfo ENOTFOUND update.code.visualstudio.com"
```

### Linting

```bash
pnpm eslint .
```

**Pre-existing Issues:** 22 ESLint errors (missing return types) - DO NOT block on these.
**Strict Rules:** Explicit function return types, no-any.
**Before commit:** Run `pnpm eslint .` - ensure NO NEW errors.

## Project Structure

### Monorepo Layout
```
├── .github/workflows/ci.yml # CI: test + lint jobs
├── packages/ # 5 packages
│ ├── langium-crmscript/ # CRMScript LSP server (Langium)
│ ├── language-server/ # TypeScript LSP server (Volar)
│ ├── superofficedx-vscode-core/ # Core extension (auth, tree view, commands)
│ ├── superofficedx-vscode-crmscript/ # CRMScript client
│ └── superofficedx-vscode-tsfso/ # TypeScript client
├── test/ # Extension dev workspace
├── eslint.config.mjs, package.json, pnpm-workspace.yaml, tsconfig.base.json, vitest.config.ts
```

### Package Details

#### 1. `superofficedx-vscode-core` (Core Extension)
Authentication, script browsing/execution for SuperOffice.

**Structure:** `src/extension.ts` (entry) | `commands/` | `providers/` (TreeView, auth, virtual FS) | `services/` (HTTP, FS, auth, Node) | `handlers/` | `container/` (DI) | `scripts/build.js` (esbuild + copy resources)
**Output:** `dist/extension.js`

#### 2. `language-server` (TypeScript LSP Server)
Volar-based LSP for TypeScript.

**Structure:** `src/server.ts` (entry) | `languagePlugin.ts` | `languageService.ts` | `typescriptPlugin.ts` | `parser/` | `tests/`
**Build:** None (bundled by vscode-tsfso)

#### 3. `superofficedx-vscode-tsfso` (TypeScript Client)
LSP client for .tsfso files.
**Structure:** `src/extension.ts` | `scripts/build.js` (bundles extension + language-server) | `syntaxes/tsfso.tmLanguage.json`
**Output:** `dist/extension.js`, `dist/server.js`

#### 4. `langium-crmscript` (CRMScript LSP Server)
Langium-based LSP for CRMScript.
**Structure:** `src/language/*.langium` (grammars) | `generated/` (AUTO-GENERATED - DO NOT EDIT) | `main.ts` | `langium-config.json` | `examples/`
**Build:** `pnpm run langium:generate` (generates TS from .langium)

#### 5. `superofficedx-vscode-crmscript` (CRMScript Client)
LSP client for .crmscript files.
**Structure:** `src/main.ts` | `scripts/esbuild.mjs` (bundles extension + langium server) | `syntaxes/`
**Output:** `out/*.cjs` (CommonJS for VS Code)

## CI/CD Pipeline

`.github/workflows/ci.yml` runs on push/PR to `main`, `develop`:
- **Test Job:** pnpm@8 + Node 20.x → `pnpm install` → build:vscode-core + build:vscode-tsfso → `pnpm test`
- **Lint Job:** pnpm@8 + Node 20.x → `pnpm install` → `pnpm eslint .`
- Jobs run independently (lint failures don't block tests)

## Common Issues

1. **Build fails:** Build langium-crmscript first: `pnpm run build:langium-crmscript`
2. **pnpm not found:** Install globally: `npm install -g pnpm@8`
3. **TSC errors:** Ensure package `tsconfig.json` extends `../../tsconfig.base.json`
4. **VS Code tests fail offline:** Expected. Skip `test:core` in CI/sandboxed environments
5. **Generated files modified:** Run `pnpm run build:langium-crmscript` to regenerate

## Development Workflow

1. `pnpm install`
2. Make changes to source files
3. Build affected packages: `build:vscode-core` / `build:vscode-tsfso` / `build:langium-crmscript`
4. Run `pnpm test` and `pnpm eslint .`
5. Debug via `.vscode/launch.json` configs: "superofficedx-vscode-core" / "tsfso" / "crmscript"

**Rules:**
- ❌ Don't edit `packages/langium-crmscript/src/language/generated/` (auto-generated)
- ❌ Don't edit `dist/` or `out/` (build outputs)
- ✅ Add explicit return types to functions
- ✅ Run `pnpm eslint .` before committing

## Pre-Commit Checklist

1. ✅ `pnpm install` (if dependencies changed)
2. ✅ Build affected packages (correct order!)
3. ✅ `pnpm test` (7 tests must pass)
4. ✅ `pnpm eslint .` (no NEW errors; 22 pre-existing OK)
5. ✅ Verify `dist/`/`out/` outputs exist
6. ✅ Don't commit generated files or build artifacts

**Trust these instructions** - validated through testing. Only explore further if information is incomplete or incorrect.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ out
test
packages/node_app/
packages/superofficedx-vscode-core/tsconfig.tsbuildinfo
packages/langium-crmscript/src/language/generated/
coverage
35 changes: 1 addition & 34 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,7 @@ export default tseslint.config(
rules: {
// TypeScript strict rules
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-explicit-any': 'error',

// Enforce barrel imports (imports from index.ts files)
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['**/services/*', '!**/services/index', '!**/services'],
message: 'Import from services barrel (../../services) instead of direct service files'
},
{
group: ['**/providers/*', '!**/providers/index', '!**/providers'],
message: 'Import from providers barrel (../../providers) instead of direct provider files'
},
{
group: ['**/types/*', '!**/types/index', '!**/types'],
message: 'Import from types barrel (../../types) instead of direct type files'
},
{
group: ['**/commands/*', '!**/commands/index', '!**/commands'],
message: 'Import from commands barrel (../../commands) instead of direct command files'
},
{
group: ['**/container/*', '!**/container/index', '!**/container'],
message: 'Import from container barrel (../../container) instead of direct container files'
},
{
group: ['**/handlers/*', '!**/handlers/index', '!**/handlers'],
message: 'Import from handlers barrel (../../handlers) instead of direct handler files'
}
]
}
]
'@typescript-eslint/no-explicit-any': 'error'
}
}
);
59 changes: 3 additions & 56 deletions packages/langium-crmscript/src/language/generated/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function isClassMember(item: unknown): item is ClassMember {
return reflection.isInstance(item, ClassMember);
}

export type DefinitionElement = Globals | NamedElement | TypeOne;
export type DefinitionElement = Globals | NamedElement;

export const DefinitionElement = 'DefinitionElement';

Expand Down Expand Up @@ -442,18 +442,6 @@ export function isStringExpression(item: unknown): item is StringExpression {
return reflection.isInstance(item, StringExpression);
}

export interface TypeOne extends AstNode {
readonly $type: 'TypeOne' | 'TypeTwo';
name: string;
type: Reference<Class>;
}

export const TypeOne = 'TypeOne';

export function isTypeOne(item: unknown): item is TypeOne {
return reflection.isInstance(item, TypeOne);
}

export interface UnaryExpression extends AstNode {
readonly $container: BinaryExpression | ExpressionBlock | ForStatement | Grammar | IfStatement | MemberCall | ReturnStatement | UnaryExpression | VariableDeclaration | WhileStatement;
readonly $type: 'UnaryExpression';
Expand Down Expand Up @@ -497,18 +485,6 @@ export function isWhileStatement(item: unknown): item is WhileStatement {
return reflection.isInstance(item, WhileStatement);
}

export interface TypeTwo extends TypeOne {
readonly $type: 'TypeTwo';
name: string;
type: Reference<Enum>;
}

export const TypeTwo = 'TypeTwo';

export function isTypeTwo(item: unknown): item is TypeTwo {
return reflection.isInstance(item, TypeTwo);
}

export type CrmscriptAstType = {
BinaryExpression: BinaryExpression
BooleanExpression: BooleanExpression
Expand Down Expand Up @@ -541,8 +517,6 @@ export type CrmscriptAstType = {
Statement: Statement
StringExpression: StringExpression
Type: Type
TypeOne: TypeOne
TypeTwo: TypeTwo
UnaryExpression: UnaryExpression
VariableDeclaration: VariableDeclaration
WhileStatement: WhileStatement
Expand All @@ -551,7 +525,7 @@ export type CrmscriptAstType = {
export class CrmscriptAstReflection extends AbstractAstReflection {

getAllTypes(): string[] {
return [BinaryExpression, BooleanExpression, Class, ClassMember, Constructor, ConstructorCall, DefinitionElement, DefinitionUnit, Enum, EnumMember, Expression, ExpressionBlock, FieldMember, ForExecution, ForStatement, FunctionDeclaration, Globals, Grammar, IfStatement, Include, MemberCall, MethodMember, NamedElement, NilExpression, NumberExpression, Parameter, PrintStatement, ReturnStatement, Statement, StringExpression, Type, TypeOne, TypeTwo, UnaryExpression, VariableDeclaration, WhileStatement];
return [BinaryExpression, BooleanExpression, Class, ClassMember, Constructor, ConstructorCall, DefinitionElement, DefinitionUnit, Enum, EnumMember, Expression, ExpressionBlock, FieldMember, ForExecution, ForStatement, FunctionDeclaration, Globals, Grammar, IfStatement, Include, MemberCall, MethodMember, NamedElement, NilExpression, NumberExpression, Parameter, PrintStatement, ReturnStatement, Statement, StringExpression, Type, UnaryExpression, VariableDeclaration, WhileStatement];
}

protected override computeIsSubtype(subtype: string, supertype: string): boolean {
Expand Down Expand Up @@ -589,16 +563,12 @@ export class CrmscriptAstReflection extends AbstractAstReflection {
case FunctionDeclaration: {
return this.isSubtype(NamedElement, supertype) || this.isSubtype(Type, supertype);
}
case Globals:
case TypeOne: {
case Globals: {
return this.isSubtype(DefinitionElement, supertype);
}
case NamedElement: {
return this.isSubtype(DefinitionElement, supertype) || this.isSubtype(Type, supertype);
}
case TypeTwo: {
return this.isSubtype(TypeOne, supertype);
}
default: {
return false;
}
Expand All @@ -614,8 +584,6 @@ export class CrmscriptAstReflection extends AbstractAstReflection {
case 'Globals:returnType':
case 'MethodMember:returnType':
case 'Parameter:type':
case 'TypeOne:type':
case 'TypeTwo:type':
case 'VariableDeclaration:type': {
return Class;
}
Expand All @@ -625,9 +593,6 @@ export class CrmscriptAstReflection extends AbstractAstReflection {
case 'MemberCall:element': {
return NamedElement;
}
case 'TypeTwo:type': {
return Enum;
}
default: {
throw new Error(`${referenceId} is not a valid reference id.`);
}
Expand Down Expand Up @@ -871,15 +836,6 @@ export class CrmscriptAstReflection extends AbstractAstReflection {
]
};
}
case TypeOne: {
return {
name: TypeOne,
properties: [
{ name: 'name' },
{ name: 'type' }
]
};
}
case UnaryExpression: {
return {
name: UnaryExpression,
Expand Down Expand Up @@ -911,15 +867,6 @@ export class CrmscriptAstReflection extends AbstractAstReflection {
]
};
}
case TypeTwo: {
return {
name: TypeTwo,
properties: [
{ name: 'name' },
{ name: 'type' }
]
};
}
default: {
return {
name: type,
Expand Down
Loading
Loading