diff --git a/.vscodeignore b/.vscodeignore index 65c609c..6528078 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,8 +1,11 @@ # Project +.bin .gitignore +.github/** .eslintignore .vscode/** .travis.yml +scripts/** **/tsconfig.json **/tsconfig.base.json contributing.md @@ -26,6 +29,9 @@ sample*/** **/test/** **/testFixture/** +# Node modules +node_modules/antlr4ng-cli/** + # Client node modules client/node_modules/** !client/node_modules/vscode-jsonrpc/** @@ -34,3 +40,11 @@ client/node_modules/** !client/node_modules/vscode-languageserver-types/** !client/node_modules/{minimatch,brace-expansion,concat-map,balanced-match}/** !client/node_modules/{semver,lru-cache,yallist}/** + +# Server node modules +server/node_modules/** +!server/node_modules/vscode-jsonrpc/** +!server/node_modules/vscode-languageserver/** +!server/node_modules/vscode-languageserver-protocol/** +!server/node_modules/vscode-languageserver-textdocument/** +!server/node_modules/vscode-languageserver-types/** diff --git a/package-lock.json b/package-lock.json index 24d7a2c..e8fa4c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,14 @@ "version": "1.4.2", "hasInstallScript": true, "license": "MIT", + "dependencies": { + "antlr4ng": "^3.0.4" + }, "devDependencies": { "@types/mocha": "^9.1.0", "@types/node": "^16.11.7", "@typescript-eslint/eslint-plugin": "^5.30.0", "@typescript-eslint/parser": "^5.30.0", - "antlr4ng": "^3.0.4", "antlr4ng-cli": "^2.0.0", "eslint": "^8.13.0", "js-yaml": "^4.1.0", @@ -25,7 +27,7 @@ "vscode": "^1.63.0" } }, - "../../../Users/svand/Downloads/antlr4ng-3.0.4": { + "../../Users/svand/Downloads/antlr4ng-3.0.4": { "name": "antlr4ng", "version": "3.0.4", "extraneous": true, @@ -477,7 +479,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/antlr4ng/-/antlr4ng-3.0.4.tgz", "integrity": "sha512-u1Ww6wVv9hq70E9AaYe5qW3ba8hvnjJdO3ZsKnb3iJWFV/medLEEhbyWwXCvvD2ef0ptdaiIUgmaazS/WE6uyQ==", - "dev": true, "license": "BSD-3-Clause", "peerDependencies": { "antlr4ng-cli": "^2.0.0" @@ -487,7 +488,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/antlr4ng-cli/-/antlr4ng-cli-2.0.0.tgz", "integrity": "sha512-oAt5OSSYhRQn1PgahtpAP4Vp3BApCoCqlzX7Q8ZUWWls4hX59ryYuu0t7Hwrnfk796OxP/vgIJaqxdltd/oEvQ==", - "dev": true, "license": "BSD-3-Clause", "bin": { "antlr4ng": "index.js" @@ -2441,14 +2441,12 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/antlr4ng/-/antlr4ng-3.0.4.tgz", "integrity": "sha512-u1Ww6wVv9hq70E9AaYe5qW3ba8hvnjJdO3ZsKnb3iJWFV/medLEEhbyWwXCvvD2ef0ptdaiIUgmaazS/WE6uyQ==", - "dev": true, "requires": {} }, "antlr4ng-cli": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/antlr4ng-cli/-/antlr4ng-cli-2.0.0.tgz", - "integrity": "sha512-oAt5OSSYhRQn1PgahtpAP4Vp3BApCoCqlzX7Q8ZUWWls4hX59ryYuu0t7Hwrnfk796OxP/vgIJaqxdltd/oEvQ==", - "dev": true + "integrity": "sha512-oAt5OSSYhRQn1PgahtpAP4Vp3BApCoCqlzX7Q8ZUWWls4hX59ryYuu0t7Hwrnfk796OxP/vgIJaqxdltd/oEvQ==" }, "anymatch": { "version": "3.1.2", diff --git a/server/src/antlr/vba.g4 b/server/src/antlr/vba.g4 index 1de110e..524fe81 100644 --- a/server/src/antlr/vba.g4 +++ b/server/src/antlr/vba.g4 @@ -101,8 +101,8 @@ nameAttr //--------------------------------------------------------------------------------------- // 5.1 Module Body Structure // Everything from here down is user generated code. -proceduralModuleBody: proceduralModuleDeclarationSection? endOfLine* proceduralModuleCode; -classModuleBody: classModuleDeclarationSection? classModuleCode; +proceduralModuleBody: proceduralModuleCode; +classModuleBody: classModuleCode; unrestrictedName : reservedIdentifier | name @@ -121,17 +121,16 @@ untypedName //--------------------------------------------------------------------------------------- // 5.2 Module Declaration Section Structure -proceduralModuleDeclarationSection - : (endOfLine+ proceduralModuleDeclarationElement)+ - | ((endOfLine+ proceduralModuleDirectiveElement)* endOfLine+ defDirective) (proceduralModuleDeclarationElement endOfLineNoWs)* - ; -classModuleDeclarationSection - : (classModuleDeclarationElement endOfLine+)+ - | ((classModuleDirectiveElement endOfLine+)* defDirective) (classModuleDeclarationElement endOfLine+)* - ; +// proceduralModuleDeclarationSection +// : (endOfLine+ proceduralModuleDeclarationElement)+ +// | ((endOfLine+ proceduralModuleDirectiveElement)* endOfLine+ defDirective) (proceduralModuleDeclarationElement endOfLineNoWs)* +// ; +// classModuleDeclarationSection +// : (classModuleDeclarationElement endOfLine+)+ +// | ((classModuleDirectiveElement endOfLine+)* defDirective) (classModuleDeclarationElement endOfLine+)* +// ; proceduralModuleDirectiveElement - : commonOptionDirective - | optionPrivateDirective + : optionPrivateDirective | defDirective ; proceduralModuleDeclarationElement @@ -140,18 +139,15 @@ proceduralModuleDeclarationElement | publicConstDeclaration | publicExternalProcedureDeclaration | globalEnumDeclaration - | commonOptionDirective | optionPrivateDirective ; classModuleDirectiveElement - : commonOptionDirective - | defDirective + : defDirective | implementsDirective ; classModuleDeclarationElement : commonModuleDeclarationElement | eventDeclaration - | commonOptionDirective | implementsDirective ; @@ -360,8 +356,13 @@ classModuleCodeElement // Added AttributeStatement. commonModuleCodeElement : remStatement - | procedureDeclaration | attributeStatement + | procedureDeclaration + | commonOptionDirective + | proceduralModuleDirectiveElement + | proceduralModuleDeclarationElement + | classModuleDirectiveElement + | classModuleDeclarationElement ; procedureDeclaration : subroutineDeclaration diff --git a/server/src/capabilities/diagnostics.ts b/server/src/capabilities/diagnostics.ts index 0263d96..cac929d 100644 --- a/server/src/capabilities/diagnostics.ts +++ b/server/src/capabilities/diagnostics.ts @@ -82,4 +82,13 @@ export class MissingOptionExplicitDiagnostic extends BaseDiagnostic { constructor(range: Range) { super(range); } +} + +export class ElementOutOfPlaceDiagnostic extends BaseDiagnostic { + message: string; + severity = DiagnosticSeverity.Error; + constructor(range: Range, elementName: string) { + super(range); + this.message = `${elementName}s cannot appear below a Sub, Function, or Property declaration.` + } } \ No newline at end of file diff --git a/server/src/project/elements/memory.ts b/server/src/project/elements/memory.ts index e73ca83..a69c160 100644 --- a/server/src/project/elements/memory.ts +++ b/server/src/project/elements/memory.ts @@ -8,7 +8,7 @@ import { ScopeElement } from './special'; import { VbaClassDocument, VbaModuleDocument } from '../document'; import { SymbolInformationFactory } from '../../capabilities/symbolInformation'; import '../../extensions/parserExtensions'; -import { DuplicateDeclarationDiagnostic } from '../../capabilities/diagnostics'; +import { DuplicateDeclarationDiagnostic, ElementOutOfPlaceDiagnostic } from '../../capabilities/diagnostics'; @@ -215,39 +215,49 @@ abstract class BaseEnumDeclarationElement extends ScopeElement implements HasSem } -export class EnumDeclarationElement extends BaseEnumDeclarationElement implements ScopeElement { +export class EnumDeclarationElement extends BaseEnumDeclarationElement implements ScopeElement, HasDiagnosticCapability { + diagnostics: Diagnostic[] = []; tokenType: SemanticTokenTypes; + isDeclaredAfterMethod: boolean; + + get symbolInformation(): SymbolInformation { + return SymbolInformationFactory.create( + this, SymbolKind.Enum + ); + } - constructor(context: EnumDeclarationContext, document: TextDocument) { + constructor(context: EnumDeclarationContext, document: TextDocument, isDeclaredAfterMethod: boolean) { super(context, document); this.tokenType = SemanticTokenTypes.enum; + this.isDeclaredAfterMethod = isDeclaredAfterMethod; this.identifier = new IdentifierElement(context.untypedName().ambiguousIdentifier()!, document); context.enumMemberList().enumElement().forEach(enumElementContext => this.pushDeclaredName(new EnumMemberDeclarationElement(enumElementContext.enumMember()!, document)) ); } - get symbolInformation(): SymbolInformation { - return SymbolInformationFactory.create( - this, SymbolKind.Enum - ); + evaluateDiagnostics(): void { + if (this.isDeclaredAfterMethod) { + this.diagnostics.push(new ElementOutOfPlaceDiagnostic(this.range, 'Enum declaration')); + } } + } class EnumMemberDeclarationElement extends BaseEnumDeclarationElement { tokenType: SemanticTokenTypes; - constructor(context: EnumMemberContext, document: TextDocument) { - super(context, document); - this.tokenType = SemanticTokenTypes.enumMember; - this.identifier = new IdentifierElement(context.untypedName().ambiguousIdentifier()!, document); - } - get symbolInformation(): SymbolInformation { return SymbolInformationFactory.create( this, SymbolKind.EnumMember ); } + + constructor(context: EnumMemberContext, document: TextDocument) { + super(context, document); + this.tokenType = SemanticTokenTypes.enumMember; + this.identifier = new IdentifierElement(context.untypedName().ambiguousIdentifier()!, document); + } } abstract class BaseVariableDeclarationStatementElement extends BaseContextSyntaxElement implements HasSemanticToken, HasSymbolInformation, NamedSyntaxElement { diff --git a/server/src/project/elements/module.ts b/server/src/project/elements/module.ts index 727bea3..c096d87 100644 --- a/server/src/project/elements/module.ts +++ b/server/src/project/elements/module.ts @@ -12,9 +12,11 @@ abstract class BaseModuleElement extends ScopeElement implements HasSymbolInform protected abstract _name: string; symbolKind: SymbolKind; diagnostics: Diagnostic[] = []; + context: ProceduralModuleContext | ClassModuleContext; constructor(context: ProceduralModuleContext | ClassModuleContext, document: TextDocument, symbolKind: SymbolKind) { super(context, document); + this.context = context; this.symbolKind = symbolKind; } @@ -29,6 +31,32 @@ abstract class BaseModuleElement extends ScopeElement implements HasSymbolInform } abstract evaluateDiagnostics(): void; + + protected get _hasOptionExplicit(): boolean { + const getCodeElements = () => { + if (this._isClassModule(this.context)) { + return this.context.classModuleBody().classModuleCode().classModuleCodeElement() + } + return this.context.proceduralModuleBody().proceduralModuleCode().proceduralModuleCodeElement(); + } + const codeElements = getCodeElements() + if (!codeElements) { + return false; + } + + for (const declaration of codeElements) { + const element = declaration.commonModuleCodeElement(); + if (element && element.commonOptionDirective()?.optionExplicitDirective()) { + return true; + } + } + + return false; + } + + private _isClassModule(context: ProceduralModuleContext | ClassModuleContext): context is ClassModuleContext { + return 'classModuleHeader' in context; + } } export class ModuleElement extends BaseModuleElement { @@ -42,7 +70,7 @@ export class ModuleElement extends BaseModuleElement { } evaluateDiagnostics(): void { - if (!this._hasOptionExplicit()) { + if (!this._hasOptionExplicit) { const header = this.context.proceduralModuleHeader(); const startLine = header.stop?.line ?? 0 + 1; this.diagnostics.push(new MissingOptionExplicitDiagnostic( @@ -67,21 +95,6 @@ export class ModuleElement extends BaseModuleElement { return name?.stripQuotes() ?? 'Unknown Module'; } - - private _hasOptionExplicit(): boolean { - const moduleDeclarations = this.context.proceduralModuleBody().proceduralModuleDeclarationSection()?.proceduralModuleDeclarationElement(); - if (!moduleDeclarations) { - return false; - } - - for (const declaration of moduleDeclarations) { - if (declaration.commonOptionDirective()?.optionExplicitDirective()) { - return true; - } - } - - return false; - } } export class ClassElement extends BaseModuleElement { @@ -95,7 +108,7 @@ export class ClassElement extends BaseModuleElement { } evaluateDiagnostics(): void { - if (!this._hasOptionExplicit()) { + if (!this._hasOptionExplicit) { const header = this.context.classModuleHeader(); const startLine = header.stop?.line ?? 0 + 1; this.diagnostics.push(new MissingOptionExplicitDiagnostic( @@ -121,21 +134,6 @@ export class ClassElement extends BaseModuleElement { const nameAttribute = nameAttributes[0]; return nameAttribute.STRINGLITERAL().getText().stripQuotes(); } - - private _hasOptionExplicit(): boolean { - const moduleDeclarations = this.context.classModuleBody().classModuleDeclarationSection()?.classModuleDeclarationElement(); - if (!moduleDeclarations) { - return false; - } - - for (const declaration of moduleDeclarations) { - if (declaration.commonOptionDirective()?.optionExplicitDirective()) { - return true; - } - } - - return false; - } } export class IgnoredAttributeElement extends BaseContextSyntaxElement implements HasDiagnosticCapability { @@ -148,7 +146,7 @@ export class IgnoredAttributeElement extends BaseContextSyntaxElement implements evaluateDiagnostics(): void { this.diagnostics.push( new IgnoredAttributeDiagnostic(this.range) - ) + ); } } diff --git a/server/src/project/parser/vbaSyntaxParser.ts b/server/src/project/parser/vbaSyntaxParser.ts index 304e5ff..2ba71fd 100644 --- a/server/src/project/parser/vbaSyntaxParser.ts +++ b/server/src/project/parser/vbaSyntaxParser.ts @@ -1,7 +1,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import { vbaLexer } from '../../antlr/out/vbaLexer'; -import { ClassModuleContext, ConstItemContext, EnumDeclarationContext, IgnoredAttrContext, ProceduralModuleContext, ProceduralModuleDeclarationElementContext, ProcedureDeclarationContext, UdtDeclarationContext, WhileStatementContext, vbaParser } from '../../antlr/out/vbaParser'; +import { ClassModuleContext, ConstItemContext, EnumDeclarationContext, IgnoredAttrContext, ProceduralModuleContext, ProcedureDeclarationContext, UdtDeclarationContext, WhileStatementContext, vbaParser } from '../../antlr/out/vbaParser'; import { vbaListener } from '../../antlr/out/vbaListener'; import { VbaClassDocument, VbaModuleDocument } from '../document'; @@ -87,6 +87,7 @@ class VbaParser extends vbaParser { class VbaListener extends vbaListener { document: VbaClassDocument | VbaModuleDocument; + protected _isAfterMethodDeclaration = false; constructor(document: VbaClassDocument | VbaModuleDocument) { super(); @@ -94,11 +95,12 @@ class VbaListener extends vbaListener { } enterEnumDeclaration = (ctx: EnumDeclarationContext) => { - const element = new EnumDeclarationElement(ctx, this.document.textDocument); + const element = new EnumDeclarationElement(ctx, this.document.textDocument, this._isAfterMethodDeclaration); this.document.registerFoldableElement(element) .registerScopedElement(element) .registerSemanticToken(element) - .registerSymbolInformation(element); + .registerSymbolInformation(element) + .registerDiagnosticElement(element); element.declaredNames.forEach(names => names.forEach(name => this.document .registerSemanticToken(name) @@ -156,6 +158,7 @@ class VbaListener extends vbaListener { }; exitProcedureDeclaration = (ctx: ProcedureDeclarationContext) => { + this._isAfterMethodDeclaration = true; this.document.deregisterScopedElement(); };