diff --git a/extension/src/astObjects/dclAtom.ts b/extension/src/astObjects/dclAtom.ts new file mode 100644 index 00000000..0687b428 --- /dev/null +++ b/extension/src/astObjects/dclAtom.ts @@ -0,0 +1,53 @@ +import { Position, Range } from 'vscode'; +import { IDclFragment } from './dclInterfaces'; + +export class DclAtom implements IDclFragment { + public symbol: string; + public flatIndex: number; + + private _line: number; + get line(): number { + return this._line; + } + + private _column: number; + get column(): number { + return this._column; + } + + constructor(line: number, column: number, value: string, flatIdx: number) { + this._line = line; + this._column = column; + this.symbol = value; + this.flatIndex = flatIdx; + } + + get isComment(): boolean { + return this.symbol.startsWith('/'); + } + + get isBlockComment(): boolean { + return this.symbol.startsWith('/*'); + } + + get isString(): boolean { + return /^".*"$/.test(this.symbol); + } + + get range(): Range { + return new Range(this.line, this.column, this.line, this.column + this.symbol.length); + } + + equal(atom: IDclFragment): boolean { + return JSON.stringify(this) === JSON.stringify(atom); + } + + contains(pos: Position): boolean { + return this.range.contains(pos); + } + + getAtomFromPosition(position: Position): IDclFragment { + return this.contains(position) ? this : null; + } + +} \ No newline at end of file diff --git a/extension/src/astObjects/dclAttribute.ts b/extension/src/astObjects/dclAttribute.ts new file mode 100644 index 00000000..8da38d92 --- /dev/null +++ b/extension/src/astObjects/dclAttribute.ts @@ -0,0 +1,127 @@ +import { Position, Range } from 'vscode'; +import { DclAtom } from './dclAtom'; +import { IDclContainer, IDclFragment } from './dclInterfaces'; + +export class DclAttribute implements IDclContainer { + + get line(): number { + return this.length === 0 ? -1 : this.firstAtom.line; + } + + get column(): number { + return this.length === 0 ? -1 : this.firstAtom.column; + } + + get asAttribute(): DclAttribute { + return this; + } + + get asContainer(): IDclContainer { + return this; + } + + get isComment(): boolean { + return false; + } + + get isBlockComment(): boolean { + return false; + } + + get isString(): boolean { + return false; + } + + get range(): Range { + const lastAtom = this.atoms[this.length - 1]; + return new Range(this.firstAtom.range.start, lastAtom.range.end); + } + + equal(dclObject: IDclFragment): boolean { + return JSON.stringify(this) === JSON.stringify(dclObject); + } + + contains(position: Position): boolean { + return this.range.contains(position); + } + + getAtomFromPosition(position: Position): IDclFragment { + if (this.contains(position)) { + for (let i = 0; i < this.length; i++) { + if (!this.atoms[i].contains(position)) { + continue; + } + return this.atoms[i]; + } + } + return null; + } + + public atoms: Array; + + get length(): number { + return this.atoms.length; + } + + get firstAtom(): IDclFragment { + return this.atoms[0]; + } + + get lastAtom(): IDclFragment { + return this.atoms[this.length - 1]; + } + + get firstNonComment(): IDclFragment { + return this.length === 0 ? null : this.firstAtom; + } + + getParentFrom(from: Position|IDclFragment, tilesOnly: boolean = false): IDclContainer { + const pos = from instanceof Position ? from : from.range.start; + if (this.contains(pos)) { + return tilesOnly ? null : this; + } + return null; + } + + flatten(into?: Array): Array { + if (!into) { + into = []; + } + this.atoms.forEach(item => { + into.push(item as DclAtom); + }); + return into; + } + + + // Everything above this point is sequenced by IDclFragment & then IDclContainer contract + // Everything below this point is unique to DclAttribute and not an interface requirement + + + constructor(atoms: Array) { + this.atoms = [...atoms]; + } + + get key(): IDclFragment { + return this.length === 0 ? null : this.firstAtom; + } + get delineator(): IDclFragment { + return this.length < 3 ? null : this.atoms[1]; + } + get value(): IDclFragment { + return this.length < 3 ? null : this.atoms[2]; + } + + // Context: DCL Attributes are valid in 2 arrangements: "Key = Value;" OR "key;" + // Any other arrangement should be considered a malformed syntax error. + get isWellFormed(): boolean { + const invalid = this.length < 2 // probably has key, but missing semi-colon + || this.length === 3 // probably missing equal or semi-colon + || this.length > 4 // exceeds possible key-value-pair structure + || this.lastAtom.symbol !== ';' // invalid attribute termination + || this.firstAtom.isString // strings are invalid keys + || (this.length === 4 && this.atoms[1].symbol !== '='); + return !invalid; + } + +} diff --git a/extension/src/astObjects/dclInterfaces.ts b/extension/src/astObjects/dclInterfaces.ts new file mode 100644 index 00000000..0e29665a --- /dev/null +++ b/extension/src/astObjects/dclInterfaces.ts @@ -0,0 +1,32 @@ +import { Position, Range } from 'vscode'; +import { DclAtom } from './dclAtom'; +import { DclAttribute } from './dclAttribute'; +import { DclTile } from './dclTile'; + +export interface IDclFragment { + readonly line: number; + readonly column: number; + readonly symbol?: string | undefined; + readonly flatIndex?: number | undefined; + readonly asTile?: DclTile | undefined; + readonly asAttribute?: DclAttribute | undefined; + readonly asContainer?: IDclContainer | undefined; + readonly isComment: boolean; + readonly isBlockComment: boolean; + readonly isString: boolean; + readonly range: Range; + + equal(atom: IDclFragment): boolean; + contains(position: Position): boolean; + getAtomFromPosition(position: Position): IDclFragment; +} + +export interface IDclContainer extends IDclFragment { + readonly atoms: Array; + readonly length: number; + readonly firstAtom: IDclFragment; + readonly lastAtom: IDclFragment; + readonly firstNonComment: IDclFragment; + getParentFrom(position: Position|IDclFragment, tilesOnly: boolean): IDclContainer; + flatten(into?: Array): Array; +} diff --git a/extension/src/astObjects/dclTile.ts b/extension/src/astObjects/dclTile.ts new file mode 100644 index 00000000..4d131fa1 --- /dev/null +++ b/extension/src/astObjects/dclTile.ts @@ -0,0 +1,224 @@ +import { Position, Range } from 'vscode'; +import { DclAtom } from './dclAtom'; +import { DclAttribute } from './dclAttribute'; +import { IDclContainer, IDclFragment } from './dclInterfaces'; + + +export class DclTile implements IDclContainer { + + get line(): number { + return this.firstAtom.line; + } + + get column(): number { + return this.firstAtom.column; + } + + get asTile(): DclTile { + return this; + } + + get asContainer(): IDclContainer { + return this; + } + + get isComment(): boolean { + return false; + } + + get isBlockComment(): boolean { + return false; + } + + get isString(): boolean { + return false; + } + + get range(): Range { + const begin = this.firstAtom.range.start; + const close = this.lastAtom.range.end; + return new Range(begin, close); + } + + equal(atom: IDclFragment): boolean { + return JSON.stringify(this) === JSON.stringify(atom); + } + + contains(position: Position): boolean { + return this.range.contains(position); + } + + getAtomFromPosition(position: Position): IDclFragment { + if (this.contains(position)) { + for (let i = 0; i < this.length; i++) { + if (!this.atoms[i].contains(position)) { + continue; + } + return this.atoms[i].getAtomFromPosition(position); + } + } + return null; + } + + public atoms: Array; + + get length(): number { + return this.atoms?.length ?? 0; + } + + get firstAtom(): IDclFragment { + return this.atoms[0].asContainer?.firstAtom ?? this.atoms[0]; + } + + get lastAtom(): IDclFragment { + return this.atoms[this.length - 1].asContainer?.lastAtom ?? this.atoms[this.length - 1]; + } + + get firstNonComment(): IDclFragment { + for (let i = 0; i < this.length; i++) { + const item = this.atoms[i]; + if (item.isComment){ + continue; + } + if (item.asContainer) { + return null; + } + return this.atoms[i]; + } + return null; + } + + getParentFrom(position: Position|IDclFragment, tilesOnly = false): IDclContainer { + const pos = position instanceof Position ? position : position.range.start; + if (this.contains(pos)) { + for (let i = 0; i < this.length; i++) { + const dclObj = this.atoms[i]; + if (!dclObj.contains(pos)) { + continue; + } + if (dclObj instanceof DclAttribute) { + return tilesOnly ? this : dclObj; + } else if (dclObj instanceof DclAtom) { + return this; + } else { + return dclObj.asTile.getParentFrom(pos, tilesOnly) ?? this; + } + } + } + return null; + } + + flatten(into?: Array): Array { + if (!into) { + into = []; + } + this.atoms.forEach(item => { + if (item.asContainer) { + item.asContainer.flatten(into); + } else if (item instanceof DclAtom) { + into.push(item); + } + }); + return into; + } + + + // Everything above this point is sequenced by IDclFragment & then IDclContainer contract + // Everything below this point is unique to DclTile and not an interface requirement + + + public linefeed: string; + + constructor(lineEnding: string, atoms: Array) { + this.linefeed = lineEnding; + this.atoms = [...atoms]; + } + + get openBracketAtom(): IDclFragment { + for (let i = 0; i < this.length; i++) { + if (this.atoms[i].symbol === '{') { + return this.atoms[i]; + } + } + return null; + } + + get tileTypeAtom(): IDclFragment { + let prevNonComment: IDclFragment; + for (let i = 0; i < this.length; i++) { + const atom = this.atoms[i]; + if (atom.isComment) { + continue; + } + if (atom.symbol === '{') { + return prevNonComment?.symbol !== ':' ? prevNonComment : null; + } + prevNonComment = atom; + } + return null; + } + + get dialogNameAtom(): IDclFragment { + const typeValue = this.tileTypeAtom?.symbol.toUpperCase() ?? ''; + const firstValue = this.firstNonComment?.symbol ?? ''; + if (typeValue !== 'DIALOG' || !/\w+.*/.test(firstValue)) { + return null; + } + return this.firstNonComment; + } + + get closeBracketAtom(): IDclFragment { + return this.atoms[this.length - 1].symbol === '}' ? this.atoms[this.length - 1] : null; + } + + // This is only used for verification of the DCL parser, but could have future formatting usefulness. + // Note1: test files cannot contain lines with only random whitespace or readable character lines that + // include trailing whitespace because a TileContainer has no context to reproduce that. + // Note2: tabs in test files are only supported for comments and strings, all other tabs are replaced with spaces + asText(context?: IContainerStringCompilerContext): string { + let isRoot = false; + if (context === undefined) { + context = {result: '', line: 0, column: 0}; + isRoot = true; + } + this.atoms.forEach(item => { + while (context.line < item.line) { + context.result += this.linefeed; + context.line++; + context.column = 0; + } + while (context.column < item.column) { + context.result += ' '; + context.column++; + } + if (item instanceof DclTile) { + item.asText(context); + } else if (item instanceof DclAttribute) { + item.atoms.forEach(x => { + while (context.column < x.column) { + context.result += ' '; + context.column++; + } + context.result += x.symbol; + context.column += x.symbol.length; + }); + } else if (item.isBlockComment) { + const lines = item.symbol.split(this.linefeed); + context.result += item.symbol; + context.column = lines[lines.length - 1].length; + context.line += lines.length - 1; + } else { + context.result += item.symbol; + context.column += item.symbol.length; + } + }); + return isRoot ? context.result : ''; + } + +} + +interface IContainerStringCompilerContext { + result: string; + line: number; + column: number; +} \ No newline at end of file diff --git a/extension/src/parsing/dclParser.ts b/extension/src/parsing/dclParser.ts new file mode 100644 index 00000000..9aa5cc89 --- /dev/null +++ b/extension/src/parsing/dclParser.ts @@ -0,0 +1,276 @@ +import * as vscode from 'vscode'; +import { getEOL } from './shared'; +import { StringBuilder, } from '../utils'; +import { DclTile } from '../astObjects/dclTile'; +import { DclAtom } from '../astObjects/dclAtom'; +import { DclAttribute } from '../astObjects/dclAttribute'; + + +const lf = '\n'.charCodeAt(0); +const cr = '\r'.charCodeAt(0); +const openBracket = '{'.charCodeAt(0); +const closeBracket = '}'.charCodeAt(0); +const semiColon = ';'.charCodeAt(0); +const colon = ':'.charCodeAt(0); +const equalSign = '='.charCodeAt(0); +const dblQuote = '"'.charCodeAt(0); +const forwardSlash = '/'.charCodeAt(0); +const backSlash = '\\'.charCodeAt(0); +const asterisk = '*'.charCodeAt(0); + + +export function getDocumentTileContainer(DocumentOrContent: vscode.TextDocument|string): DclTile { + const ctx = new DclDocContext(DocumentOrContent); + try { + _getDclDocumentContainer(ctx); + return ctx.rootContainer; + } finally { + ctx.dispose(); + } +} + + +class DclDocContext implements vscode.Disposable { + dispose() { + this.containers.length = 0; + delete this.containers; + this.fragments.length = 0; + delete this.fragments; + delete this.data; + delete this.temp; + } + + + private containers: Array; + private fragments: Array; + private containerIndex = 0; + + get activeContainer() : DclTile { + if (this.containerIndex < this.containers.length) { + return this.containers[this.containerIndex]; + } + return null; + } + + get rootContainer(): DclTile { + return this.containers[0]; + } + + + constructor(docOrText: string|vscode.TextDocument) { + if (typeof(docOrText) === 'string') { + this.data = docOrText; + this.linefeed = (docOrText.indexOf('\r\n') >= 0 ? '\r\n' : '\n'); + } else { + this.data = docOrText.getText(); + this.linefeed = getEOL(docOrText); + } + this.temp = new StringBuilder(); + this.containers = [ new DclTile(this.linefeed, []) ]; + this.fragments = []; + } + + data: string; + linefeed: string; + temp: StringBuilder; + + dataIndex: number = 0; + line: number = 0; + column: number = 0; + + atomIndex: number = 0; + + groupLine: number = 0; + groupColumn: number = 0; + + + + + + moveNext(): DclDocContext { + this.dataIndex++; + this.column++; + return this; + } + + incrementAtLineBreak(curr: number): DclDocContext { + if (curr === lf) { + this.line++; + this.column = -1; + } + return this; + } + + private saveGroupAtom(value: string): DclAtom { + const atom = new DclAtom(this.groupLine, this.groupColumn, value, this.atomIndex++); + this.fragments.push(atom); + return atom; + } + private saveCurrentAtom(value: string): DclAtom { + const atom = new DclAtom(this.line, this.column, value, this.atomIndex++); + this.fragments.push(atom); + return atom; + } + + startGroupPointer(): DclDocContext { + this.groupColumn = this.column; + this.groupLine = this.line; + return this; + } + + saveTempData(): DclDocContext { + if (this.temp.hasValues()) { + this.saveGroupAtom(this.temp.materialize()); + } + return this; + } + + saveTempDataAndCurrentCharToFragments(): DclDocContext { + this.saveTempData(); + this.saveCurrentAtom(this.data.charAt(this.dataIndex)); + return this; + } + + saveCurrentCharToFragments(): DclDocContext { + this.saveCurrentAtom(this.data.charAt(this.dataIndex)); + return this; + } + + createNewTileScopeFromFragments(): void { + const tile = new DclTile(this.linefeed, this.fragments); + this.activeContainer.atoms.push(tile); + this.containers.push(tile); + this.fragments.length = 0; + this.containerIndex++; + } + + closeActiveTileScope(): void { + this.fragments.forEach(x => { + this.activeContainer.atoms.push(x); + }); + this.fragments.length = 0; + this.containerIndex--; + this.containers.pop(); + } + + storeChar(charCode: number) : DclDocContext { + if (!this.temp.hasValues()) { + this.startGroupPointer(); + } + this.temp.appendCode(charCode); + return this; + } + + + saveFragmentsAsAttribute() : DclDocContext { + // Forces comments into the parent Tile Container and creates Attribute from remaining elements. This only + // needs to inspect the head for comments because encountering a comment causes an attribute accumulation event. + let skip = 0; + this.fragments.forEach(atom => { + if (atom.isComment) { + this.activeContainer.atoms.push(atom); + skip++; + } + }); + if (this.fragments.length - skip > 0) { + this.activeContainer.atoms.push(new DclAttribute(this.fragments.slice(skip))); + } + this.fragments.length = 0; + return this; + } + + processCommentAndSaveAsFragment(isBlock: boolean): void { + while (this.dataIndex < this.data.length) { + const curr = this.data.charCodeAt(this.dataIndex); + const next = this.data.charCodeAt(this.dataIndex + 1); + this.storeChar(curr) + .incrementAtLineBreak(curr) // does nothing when (curr !== lf) + .moveNext(); + if (isBlock && curr === asterisk && next === forwardSlash) { + this.storeChar(next) + .moveNext(); + break; + } else if (!isBlock && (next === cr || next === lf)) { + break; + } + } + this.saveTempData(); + } + + processStringAndSaveAsFragment(): void { + this.storeChar(dblQuote) + .moveNext(); + let isEscaping = false; + + while (this.dataIndex < this.data.length) { + const curr = this.data.charCodeAt(this.dataIndex); + this.storeChar(curr) + .moveNext(); + if (curr === backSlash) { + isEscaping = !isEscaping; + } else if (curr === dblQuote && !isEscaping) { + break; + } else if (isEscaping) { + isEscaping = false; + } + } + this.saveTempData(); + } + +} + + + + +function _getDclDocumentContainer(ctx: DclDocContext) { + while (ctx.dataIndex < ctx.data.length) { + const curr = ctx.data.charCodeAt(ctx.dataIndex); + const next = ctx.data.charCodeAt(ctx.dataIndex + 1); + + if (curr === openBracket) { // tile/attribute breakpoint + ctx.saveTempDataAndCurrentCharToFragments() + .moveNext() + .createNewTileScopeFromFragments(); + } else if (curr === closeBracket) { // tile/attribute breakpoint + ctx.saveTempData() + .saveFragmentsAsAttribute() + .saveCurrentCharToFragments() + .moveNext() + .closeActiveTileScope(); + } else if (curr === semiColon) { // attribute/fragment breakpoint + ctx.saveTempDataAndCurrentCharToFragments() + .moveNext() + .saveFragmentsAsAttribute(); + } else if (curr === forwardSlash) { // attribute/fragment breakpoint + ctx.saveTempData() + .saveFragmentsAsAttribute() + .startGroupPointer() + .processCommentAndSaveAsFragment(next === asterisk); + } else if (curr === cr || curr === lf) { // attribute/fragment breakpoint + ctx.saveTempData() + .saveFragmentsAsAttribute() + .incrementAtLineBreak(curr) + .moveNext(); + } else if (curr < 33) { // fragment breakpoint - tabs, spaces & misc non-printable characters + ctx.saveTempData() + .moveNext(); + } else if (curr === dblQuote) { // fragment breakpoint + ctx.saveTempData() + .startGroupPointer() + .processStringAndSaveAsFragment(); + } else if (curr === equalSign || curr === colon) { // fragment breakpoint + ctx.saveTempDataAndCurrentCharToFragments() + .moveNext(); + } else { + ctx.storeChar(curr) + .moveNext(); + } + } + + // malformed syntax contingency so we aren't accidentally discarding any user data + ctx.saveTempData() + .saveFragmentsAsAttribute(); + return ctx.rootContainer; +} + + diff --git a/extension/src/project/readOnlyDocument.ts b/extension/src/project/readOnlyDocument.ts index 4f318eeb..5e7ca78b 100644 --- a/extension/src/project/readOnlyDocument.ts +++ b/extension/src/project/readOnlyDocument.ts @@ -6,6 +6,8 @@ import { DocumentManager } from '../documents'; import { DocumentServices } from '../services/documentServices'; import { ILispFragment } from '../astObjects/ILispFragment'; import { LispContainer } from '../astObjects/lispContainer'; +import { DclTile } from '../astObjects/dclTile'; +import * as DclParser from '../parsing/dclParser'; const localize = nls.config({ messageFormat: nls.MessageFormat.file })(); export class ReadonlyLine implements vscode.TextLine { @@ -111,10 +113,10 @@ export class ReadonlyDocument implements vscode.TextDocument { get atomsForest(): Array { if (this.languageId === DocumentManager.Selectors.lsp) { if (this._documentContainer){ - return this._documentContainer.atoms; + return this.documentContainer.atoms; } else { this.updateAtomsForest(); - return this._documentContainer.atoms; + return this.documentContainer.atoms; } } else { return []; @@ -136,7 +138,7 @@ export class ReadonlyDocument implements vscode.TextDocument { fileContent: string; lines: string[]; eolLength: number; - private _documentContainer: LispContainer; // Added to drastically reduces complexity in other places. + private _documentContainer: LispContainer|DclTile; // Added to drastically reduces complexity in other places. //#region implementing vscode.TextDocument @@ -271,10 +273,22 @@ export class ReadonlyDocument implements vscode.TextDocument { get documentContainer(): LispContainer { - if (this._documentContainer) { + if (this.languageId !== DocumentManager.Selectors.lsp) { + return null; + } else if (this._documentContainer instanceof LispContainer) { return this._documentContainer; } else { return this._documentContainer = LispParser.getDocumentContainer(this.fileContent); } } + + get documentDclContainer(): DclTile { + if (this.languageId !== DocumentManager.Selectors.dcl) { + return null; + } else if (this._documentContainer instanceof DclTile) { + return this._documentContainer; + } else { + return this._documentContainer = DclParser.getDocumentTileContainer(this.fileContent); + } + } } \ No newline at end of file diff --git a/extension/src/services/documentServices.ts b/extension/src/services/documentServices.ts index 0bda05cb..d930026e 100644 --- a/extension/src/services/documentServices.ts +++ b/extension/src/services/documentServices.ts @@ -4,7 +4,8 @@ import { SymbolManager } from '../symbols'; import { ILispFragment } from '../astObjects/ILispFragment'; - +// This namespace should probably be renamed to LspDocumentServices once the new DCL AST Objects +// find the need to for extended analytical functions. IE, avoid the namespace duality issue. export namespace DocumentServices { // DRY Technical Debt diff --git a/extension/src/test/SourceFile/renaming/dialog.dcl b/extension/src/test/SourceFile/renaming/dialog.dcl new file mode 100644 index 00000000..ad62f1e8 --- /dev/null +++ b/extension/src/test/SourceFile/renaming/dialog.dcl @@ -0,0 +1,49 @@ +/* +this is a block +comment +*/ // line comment on block tail + +ALE_Dialog1 : dialog { + // do stuff + label = "Random Choices Box"; + // and things + initial_focus = "cbxProj1"; // then disappear + : boxed_row { label = "Select an option:"; + : column { width = 40; children_alignment = right; + : text { value = "NA"; key = "lblProj3"; } + : text { value = "NA"; key = "lblProj4"; } + } + : column { width = 1; children_alignment = centered; children_fixed_width = true; horizontal_margin = 1; + : row { horizontal_margin = 0; + : toggle { action = "(DoCbxStuff \"stringArg\")"; width = 1; key = "cbxProj3"; } + : spacer { width = 2; } + : toggle { action = "(DoCbxStuff)"; width = 1; key = "cbxProj1"; } + } + : row { horizontal_margin = 0; + : toggle { action = "(DoCbxStuff)"; width = 1; key = "cbxProj4"; } + : spacer { width = 2; } + : toggle { action = "(DoCbxStuff)"; width = 1; key = "cbxProj2"; } + } + } + : column { width = 40; alignment = left; children_fixed_width = false; horizontal_margin = 0; children_alignment = left; horizontal_alignment = left; + : text { alignment = left; value = "NA"; key = "lblProj1"; } + : text { alignment = left; value = "NA"; key = "lblProj2"; } + } + } //boxed row + } + + +ALE_Dialog2 : dialog { + label = "Other Stuff Dialog"; + : boxed_row { + label = "Tell me something cool"; + : edit_box { action = "(GoDoStuff)"; key = "tbxInfo"; } + } + : row { + : button { key = "btnCancel"; label = "Cancel"; is_cancel = true; } + : spacer { width = 60; } + : button { key = "btnOkay"; label = "Send"; is_enabled = false; } + } + spacer; + ok_cancel; + } \ No newline at end of file diff --git a/extension/src/test/suite/astObjects.DclAtom.test.ts b/extension/src/test/suite/astObjects.DclAtom.test.ts new file mode 100644 index 00000000..07b7d1be --- /dev/null +++ b/extension/src/test/suite/astObjects.DclAtom.test.ts @@ -0,0 +1,67 @@ +import { assert, expect } from 'chai'; +import { DclAtom } from '../../astObjects/dclAtom'; + +suite("AST Objects: DCL Atom", function () { + + const mock0 = new DclAtom(19, 0, '/*\r\nHappy\r\n*/', 0); + const mock1 = new DclAtom(20, 21, 'new', 2022); + const mock2 = new DclAtom(20, 21, '"new"', 2022); + const mock3 = new DclAtom(20, 22, '// year', 1); + const mock4 = new DclAtom(20, 22, '// year', 1); + + + // Note: DclTile tests indirectly, but properly cover range, contains & getAtomFromPosition testing + + + test("DclAtom.isComment Property", function () { + try { + expect(mock0.isComment).to.equal(true); + expect(mock1.isComment).to.equal(false); + expect(mock2.isComment).to.equal(false); + expect(mock3.isComment).to.equal(true); + expect(mock4.isComment).to.equal(true); + } + catch (err) { + assert.fail("Mocks configured to be true or false returned opposite value"); + } + }); + + test("DclAtom.isBlockComment Property", function () { + try { + expect(mock0.isBlockComment).to.equal(true); + expect(mock1.isBlockComment).to.equal(false); + expect(mock2.isBlockComment).to.equal(false); + expect(mock3.isBlockComment).to.equal(false); + expect(mock4.isBlockComment).to.equal(false); + } + catch (err) { + assert.fail("Mocks configured to be true or false returned opposite value"); + } + }); + + test("DclAtom.isString Property", function () { + try { + expect(mock0.isString).to.equal(false); + expect(mock1.isString).to.equal(false); + expect(mock2.isString).to.equal(true); + expect(mock3.isString).to.equal(false); + expect(mock4.isString).to.equal(false); + } + catch (err) { + assert.fail("Mocks configured to be true or false returned opposite value"); + } + }); + + test("DclAtom.equal()", function () { + try { + expect(mock0.equal(mock1)).to.equal(false); + expect(mock1.equal(mock2)).to.equal(false); + expect(mock2.equal(mock3)).to.equal(false); + expect(mock3.equal(mock4)).to.equal(true); + } + catch (err) { + assert.fail("Mocks configured to be true or false returned opposite value"); + } + }); + +}); diff --git a/extension/src/test/suite/astObjects.DclAttribute.test.ts b/extension/src/test/suite/astObjects.DclAttribute.test.ts new file mode 100644 index 00000000..40293c97 --- /dev/null +++ b/extension/src/test/suite/astObjects.DclAttribute.test.ts @@ -0,0 +1,187 @@ +import * as path from 'path'; +import { assert, expect } from 'chai'; +import { ReadonlyDocument } from '../../project/readOnlyDocument'; +import { IDclContainer } from '../../astObjects/dclInterfaces'; +import { Position } from 'vscode'; +import { DclAttribute } from '../../astObjects/dclAttribute'; +import { DclAtom } from '../../astObjects/dclAtom'; + +suite("AST Objects: DCL Attribute", function () { + + let doc: ReadonlyDocument; + suiteSetup(async () => { + const extRootPath = path.resolve(__dirname, '../../../'); + const dclPath = path.resolve(extRootPath, "./extension/src/test/SourceFile/renaming/dialog.dcl"); + doc = ReadonlyDocument.open(dclPath); + }); + + + // Note: These tests in conjunction with DCL Tile Tests indirectly, but properly cover: + // range, contains, length, firstAtom, lastAtom & getAtomFromPosition + + + test("DclAttribute Misc IDclFragment Obligations", function () { + try { + const sut = (getLine: number, getColumn: number): IDclContainer => { + return doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), false); + }; + const att1 = sut(7, 7); + const att2 = sut(21, 21); + const att3 = new DclAttribute([]); + expect(att1.isComment).to.equal(false); + expect(att1.isBlockComment).to.equal(false); + expect(att1.isString).to.equal(false); + expect(att2.isComment).to.equal(false); + expect(att2.isBlockComment).to.equal(false); + expect(att2.isString).to.equal(false); + expect(att3.line).to.equal(-1); + expect(att3.column).to.equal(-1); + } + catch (err) { + assert.fail("The expected container and/or value was not return by the test method"); + } + }); + + test("DclAttribute.flatten() Quantities", function () { + try { + const getAttFromPos = (getLine: number, getColumn: number): IDclContainer => { + return doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), false); + }; + + const sut1 = getAttFromPos(44, 30).flatten(); + expect(sut1.length).to.equal(4); + expect(getAttFromPos(44, 56).flatten(sut1).length).to.equal(8); + + const sut2 = getAttFromPos(46, 8).flatten(); + expect(sut2.length).to.equal(2); + expect(getAttFromPos(47, 8).flatten(sut2).length).to.equal(4); + } + catch (err) { + assert.fail("The known attribute returned the wrong quantity of atoms"); + } + }); + + + test("DclAttribute.equal()", function () { + try { + const sut = (getLine: number, getColumn: number): IDclContainer => { + return doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), false); + }; + const realAtt = sut(18, 30); + let realIndex = realAtt.firstAtom.flatIndex; + const mockAtt = new DclAttribute([ + new DclAtom(18, 27, 'width', realIndex), + new DclAtom(18, 33, '=', ++realIndex), + new DclAtom(18, 35, '2', ++realIndex), + new DclAtom(18, 36, ';', ++realIndex) + ]); + + expect(realAtt.equal(mockAtt)).to.equal(true); + (mockAtt.atoms[0] as any)['symbol'] = 'Width'; + expect(realAtt.equal(mockAtt)).to.equal(false); + } + catch (err) { + assert.fail("The mock failed to equal or continued to equal the known attribute before or after alterations"); + } + }); + + + test("DclAttribute.getParentFrom() TilesOnly == False", function () { + try { + const sut = (getLine: number, getColumn: number, expectLine: number, expectColumn: number): boolean => { + const pos = new Position(getLine, getColumn); + let att = doc.documentDclContainer.getParentFrom(pos); + if (att === null || att.asTile) { + return false; + } + att = att.asAttribute.getParentFrom(pos); + return att === null ? false : att.line === expectLine && att.column === expectColumn; + }; + expect(sut(4, 0, 0, 0)).to.equal(false); // reject root block comment + expect(sut(3, 22, 0, 0)).to.equal(false); // reject root comment + expect(sut(31, 16, 5, 0)).to.equal(false); // reject tile atom + expect(sut(22, 22, 22, 16)).to.equal(false); // reject tile atom + expect(sut(21, 12, 21, 12)).to.equal(false); // reject tile atom + expect(sut(38, 40, 38, 8)).to.equal(true); // accept attribute atom + expect(sut(42, 50, 42, 38)).to.equal(true); // accept attribute atom + expect(sut(46, 8, 46, 4)).to.equal(true); // accept attribute atom + } + catch (err) { + assert.fail("A known atom position did not return the expected parent position"); + } + }); + + + test("DclAttribute.firstNonComment Property", function () { + try { + const sut1 = doc.documentDclContainer.getParentFrom(new Position(36, 7), false); + const sut2 = doc.documentDclContainer.getParentFrom(new Position(42, 30), false); + expect(sut1.firstNonComment.symbol).to.equal('label'); + expect(sut2.firstNonComment.symbol).to.equal('key'); + expect(new DclAttribute([]).firstNonComment).to.equal(null); + } + catch (err) { + assert.fail("The expected container and/or string was not return by the test method"); + } + }); + + + test("DclAttribute Positional Properties", function () { + try { + const sut1 = doc.documentDclContainer.getParentFrom(new Position(44, 30), false).asAttribute; + const sut2 = doc.documentDclContainer.getParentFrom(new Position(46, 7), false).asAttribute; + const sut3 = new DclAttribute([]); + expect(sut1.key.symbol).to.equal('key'); + expect(sut2.key.symbol).to.equal('spacer'); + expect(sut3.key).to.equal(null); + expect(sut1.delineator.symbol).to.equal('='); + expect(sut2.delineator).to.equal(null); + expect(sut1.value.symbol).to.equal('"btnOkay"'); + expect(sut2.value).to.equal(null); + expect(sut1.lastAtom.symbol).to.equal(';'); + expect(sut2.lastAtom.symbol).to.equal(';'); + } + catch (err) { + assert.fail("The expected container and/or value was not returned"); + } + }); + + + test("DclAttribute.isWellFormed Property", function () { + try { + const mod = (item: IDclContainer, idx: number, value: string): void => { + (item.atoms[idx] as any).symbol = value; + }; + const mock = new DclAttribute([ + new DclAtom(18, 27, 'width', 21), + new DclAtom(18, 33, '=', 22), + new DclAtom(18, 35, '180', 23), + new DclAtom(18, 36, ';', 24) + ]); + expect(mock.isWellFormed).to.equal(true); // width = 180; + mod(mock, 1, '*'); + expect(mock.isWellFormed).to.equal(false); // width * 180; + mod(mock, 1, '='); + mod(mock, 0, '"width"'); + expect(mock.isWellFormed).to.equal(false); // "width" = 180; + mod(mock, 0, 'width'); + mock.atoms.pop(); + expect(mock.isWellFormed).to.equal(false); // width = 180 + mock.atoms.pop(); + expect(mock.isWellFormed).to.equal(false); // width = + mod(mock, 1, ';'); + expect(mock.isWellFormed).to.equal(true); // width ; + mock.atoms.pop(); + expect(mock.isWellFormed).to.equal(false); // width + + } + catch (err) { + assert.fail("At least one of the tests certifying the Attribute did not perform as expected"); + } + }); + + + + + +}); diff --git a/extension/src/test/suite/astObjects.DclTile.test.ts b/extension/src/test/suite/astObjects.DclTile.test.ts new file mode 100644 index 00000000..13c49b60 --- /dev/null +++ b/extension/src/test/suite/astObjects.DclTile.test.ts @@ -0,0 +1,238 @@ +import * as path from 'path'; +import { assert, expect } from 'chai'; +import { ReadonlyDocument } from '../../project/readOnlyDocument'; +import { DclTile } from '../../astObjects/dclTile'; +import { IDclContainer, IDclFragment } from '../../astObjects/dclInterfaces'; +import { Position } from 'vscode'; +import { DclAttribute } from '../../astObjects/dclAttribute'; +import { DclAtom } from '../../astObjects/dclAtom'; + +suite("AST Objects: DCL Tile", function () { + + let doc: ReadonlyDocument; + suiteSetup(async () => { + const extRootPath = path.resolve(__dirname, '../../../'); + const dclPath = path.resolve(extRootPath, "./extension/src/test/SourceFile/renaming/dialog.dcl"); + doc = ReadonlyDocument.open(dclPath); + }); + + + // Note: these tests indirectly, but also properly cover range, contains, length, firstAtom & lastAtom + + + test("DclTile.flatten() Quantities", function () { + try { + const sut = doc.documentDclContainer; + expect(sut.atoms[2].asTile.flatten().length).to.equal(237); + expect(sut.atoms[3].asTile.flatten().length).to.equal(77); + expect(sut.flatten().length).to.equal(316); + } + catch (err) { + assert.fail("The root or an immediate Tile did not flatten to expected quantities"); + } + }); + + + test("DclTile.getAtomFromPosition()", function () { + try { + const sut = (line: number, column: number): string => { + return doc.documentDclContainer.getAtomFromPosition(new Position(line, column)).symbol; + }; + + expect(sut(0, 0)).to.equal('/*\r\nthis is a block\r\ncomment\r\n*/'); + expect(sut(3, 10)).to.equal('// line comment on block tail'); + expect(sut(5, 16)).to.equal('dialog'); + expect(sut(7, 16)).to.equal('"Random Choices Box"'); + expect(sut(12, 35)).to.equal('key'); + expect(sut(31, 16)).to.equal('//boxed row'); + } + catch (err) { + assert.fail("A known atom position did not return the expected string value"); + } + }); + + + + test("DclTile.getParentFrom() TilesOnly == True", function () { + try { + const sut = (getLine: number, getColumn: number, expectLine: number, expectColumn: number): boolean => { + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), true); + return frag === null ? false : frag.line === expectLine && frag.column === expectColumn; + }; + expect(sut(4, 0, 0, 0)).to.equal(false); + expect(sut(3, 22, 0, 0)).to.equal(true); + expect(sut(31, 16, 5, 0)).to.equal(true); + expect(sut(22, 22, 22, 16)).to.equal(true); + expect(sut(21, 12, 21, 12)).to.equal(true); + expect(sut(38, 40, 37, 4)).to.equal(true); + expect(sut(42, 50, 42, 8)).to.equal(true); + } + catch (err) { + assert.fail("A known atom position did not return the expected parent position"); + } + }); + + test("DclTile.getParentFrom() TilesOnly == False", function () { + try { + const sut = (getLine: number, getColumn: number, expectLine: number, expectColumn: number): boolean => { + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), false); + return frag === null ? false : frag.line === expectLine && frag.column === expectColumn; + }; + expect(sut(4, 0, 0, 0)).to.equal(false); + expect(sut(3, 22, 0, 0)).to.equal(true); + expect(sut(31, 16, 5, 0)).to.equal(true); + expect(sut(22, 22, 22, 16)).to.equal(true); + expect(sut(21, 12, 21, 12)).to.equal(true); + expect(sut(38, 40, 38, 8)).to.equal(true); + expect(sut(42, 50, 42, 38)).to.equal(true); + } + catch (err) { + assert.fail("A known atom position did not return the expected parent position"); + } + }); + + + test("DclTile.firstNonComment Property", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclFragment => { + // comments cannot exist in tiles, so results should always be atom 0 when getParentFrom(false) returns an attribute + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + const container: DclTile|DclAttribute = frag.asAttribute ?? frag.asTile; + const result = container.firstNonComment; + return result; + }; + expect(sut(0, 1, true)).to.equal(null); // Root should be null since it only contains comments & containers + expect(sut(6, 12, true).symbol).to.equal('ALE_Dialog1'); + expect(sut(17, 45, true).symbol).to.equal(':'); + expect(sut(36, 22, true).symbol).to.equal('ALE_Dialog2'); + expect(sut(36, 7, false).symbol).to.equal('label'); + expect(sut(42, 30, false).symbol).to.equal('key'); + expect(sut(43, 23, false).symbol).to.equal('width'); + } + catch (err) { + assert.fail("The expected container and/or string was not return by the test method"); + } + }); + + test("DclTile.openBracketAtom Property", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclFragment => { + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + const result = frag.asTile.openBracketAtom; + return result; + }; + expect(sut(0, 1, true)).to.equal(null); // Root should be null since it only contains comments & containers + expect(sut(6, 12, true).symbol).to.equal('{'); + expect(sut(17, 45, true).symbol).to.equal('{'); + expect(sut(36, 22, true).symbol).to.equal('{'); + } + catch (err) { + assert.fail("The expected container and/or string was not return by the test method"); + } + }); + + test("DclTile.closeBracketAtom Property", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclFragment => { + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + const result = frag.asTile.closeBracketAtom; + return result; + }; + expect(sut(0, 1, true)).to.equal(null); // Root should be null since it only contains comments & containers + expect(sut(6, 12, true).symbol).to.equal('}'); + expect(sut(17, 45, true).symbol).to.equal('}'); + expect(sut(36, 22, true).symbol).to.equal('}'); + } + catch (err) { + assert.fail("The expected container and/or string was not return by the test method"); + } + }); + + + test("DclTile.dialogNameAtom Property", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclFragment => { + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + const result = frag.asTile.dialogNameAtom; + return result; + }; + expect(sut(0, 1, true)).to.equal(null); // Root should be null since it only contains comments & containers + expect(sut(6, 12, true).symbol).to.equal('ALE_Dialog1'); + expect(sut(17, 45, true)).to.equal(null); + expect(sut(36, 22, true).symbol).to.equal('ALE_Dialog2'); + } + catch (err) { + assert.fail("The expected container and/or string was not return by the test method"); + } + }); + + test("DclTile.tileTypeAtom Property", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclFragment => { + const frag = doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + const result = frag.asTile.tileTypeAtom; + return result; + }; + expect(sut(0, 1, true)).to.equal(null); // Root should be null since it only contains comments & containers + expect(sut(6, 12, true).symbol).to.equal('dialog'); + expect(sut(17, 45, true).symbol).to.equal('toggle'); + expect(sut(36, 22, true).symbol).to.equal('dialog'); + expect(sut(39, 27, true).symbol).to.equal('edit_box'); + } + catch (err) { + assert.fail("The expected container and/or string was not return by the test method"); + } + }); + + + test("DclTile Misc IDclFragment Obligations", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclContainer => { + return doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + }; + const tile1 = sut(6, 12, true); + const tile2 = sut(17, 45, true); + expect(tile1.isComment).to.equal(false); + expect(tile1.isBlockComment).to.equal(false); + expect(tile1.isString).to.equal(false); + expect(tile2.isComment).to.equal(false); + expect(tile2.isBlockComment).to.equal(false); + expect(tile2.isString).to.equal(false); + } + catch (err) { + assert.fail("The expected container and/or value was not return by the test method"); + } + }); + + test("DclTile.equal()", function () { + try { + const sut = (getLine: number, getColumn: number, getTile: boolean): IDclContainer => { + return doc.documentDclContainer.getParentFrom(new Position(getLine, getColumn), getTile); + }; + const realTile = sut(18, 20, true); + let realIndex = realTile.firstAtom.flatIndex; + const mockTile = new DclTile('\r\n', [ + new DclAtom(18, 16, ':', realIndex), + new DclAtom(18, 18, 'spacer', ++realIndex), + new DclAtom(18, 25, '{', ++realIndex), + new DclAttribute([ + new DclAtom(18, 27, 'width', ++realIndex), + new DclAtom(18, 33, '=', ++realIndex), + new DclAtom(18, 35, '2', ++realIndex), + new DclAtom(18, 36, ';', ++realIndex) + ]), + new DclAtom(18, 38, '}', ++realIndex) + ]); + + expect(realTile.equal(mockTile)).to.equal(true); + (mockTile.atoms[1] as any)['symbol'] = 'spacex'; + expect(realTile.equal(mockTile)).to.equal(false); + } + catch (err) { + assert.fail("The mock failed to equal or continued to equal the known tile before or after alterations"); + } + }); + + + +}); \ No newline at end of file diff --git a/extension/src/test/suite/parsing.dclParser.test.ts b/extension/src/test/suite/parsing.dclParser.test.ts new file mode 100644 index 00000000..6fcb8d12 --- /dev/null +++ b/extension/src/test/suite/parsing.dclParser.test.ts @@ -0,0 +1,124 @@ +import * as path from 'path'; +import { assert, expect } from 'chai'; + +import { getDocumentTileContainer } from '../../parsing/dclParser'; +import { ReadonlyDocument } from '../../project/readOnlyDocument'; +import { DclTile } from '../../astObjects/dclTile'; +import { DclAttribute } from '../../astObjects/dclAttribute'; + +const extRootPath = path.resolve(__dirname, '../../../'); +const dclPath = path.resolve(extRootPath, "./extension/src/test/SourceFile/renaming/dialog.dcl"); + + +suite("Parsing: DCL Content", function () { + + let doc: ReadonlyDocument; + suiteSetup(async () => { + doc = ReadonlyDocument.open(dclPath); + }); + + test("DclParser - getDocumentTileContainer()", function () { + try { + const start = Date.now(); + const tile = getDocumentTileContainer(doc.fileContent); + const stop = Date.now(); + const text = tile.asText(); + const conversion = Date.now(); + + const diff1 = stop - start; + const diff2 = conversion - stop; + + console.log(`\t\tDCL Parsing Time: ${diff1}ms`); + console.log(`\t\tDCL Convert Back: ${diff2}ms`); + + expect(doc.documentContainer).to.equal(null); + expect(doc.fileContent).to.equal(text); + } + catch (err) { + assert.fail("The original parsed content did not convert back to the same text"); + } + }); + + test("DCL Root Container Contents", function () { + try { + const tile = doc.documentDclContainer; + expect(tile.length).to.equal(4); + expect(tile.linefeed).to.equal('\r\n'); + expect(tile.atoms[0].line).to.equal(0); + expect(tile.atoms[0].isBlockComment).to.equal(true); + expect(tile.atoms[1].line).to.equal(3); + expect(tile.atoms[1].isComment).to.equal(true); + expect(tile.atoms[1].isBlockComment).to.equal(false); + expect(tile.atoms[2].line).to.equal(5); + expect(tile.atoms[2] instanceof DclTile).to.equal(true); + expect(tile.atoms[3].line).to.equal(35); + expect(tile.atoms[3] instanceof DclTile).to.equal(true); + expect(tile.atoms[0].symbol.split('\n').length).to.equal(4); + } + catch (err) { + assert.fail("The parsed content did not contain the expected root values"); + } + }); + + const good1 = 'myDCL : dialog {\n\tok_cancel;\n\t}'; + const good2 = 'myDCL : dialog {\n\t: button {\n\t\twidth = 100;\n\t\tlabel = "random";\n\t\t}\n\tok_cancel;\n\t}'; + const poor0 = 'myDCL : dialog {\n\tok_cancel;'; + const poor1 = 'myDCL : dialog {:button{} ok_cancel;'; + const poor2 = 'myDCL : dialog {\n\t: button {\n\t\twidth = 100;\n\t\tlabel = "random";\n\t\tok_cancel;\n\t}'; + const poor3 = 'myDCL : dialog {\n\t: button {\n\t\twidth = 100\t\t\tlabel = "random";\n\t\t}\n\tok_cancel;\n\t}'; + const poor4 = 'myDCL : dialog {\n\t: button {\n\t\twidth = 100;\n\t\tlabel "random";\n\t\t}\n\tok_cancel;'; + const poor5 = 'myDCL : dialog {\n\t: button {\n\t\t= 100;\n\t\tlabel = "random"\n\tok_cancel;\n\t}'; + const poor6 = 'myDCL : dialog {\n\tok_cancel;\n\t} randomExtra'; + + test("Malformed DCL Test #1", function () { + try { + const sut1 = getDocumentTileContainer(good1); + const sut2 = getDocumentTileContainer(poor0); + const sut3 = getDocumentTileContainer(poor1); + const sut4 = getDocumentTileContainer(poor6); + + const baseline = sut1.flatten().length; + expect(sut2.flatten().length).to.equal(baseline - 1); + expect(sut2.atoms[0].asTile.atoms[4]).instanceOf(DclAttribute); + + expect(sut4.flatten().length).to.equal(baseline + 1); + expect(sut4.length).to.equal(2); + expect(sut4.atoms[1]).instanceOf(DclAttribute); + + expect(sut3.flatten().length).to.equal(10); + expect(sut3.atoms[0].asTile.atoms[4]).instanceOf(DclTile); + expect(sut3.atoms[0].asTile.atoms[5]).instanceOf(DclAttribute); + } + catch (err) { + assert.fail("At least one test case parsed incorrectly or aggregated unexpectedly"); + } + }); + + + test("Malformed DCL Test #2", function () { + try { + const sut1 = getDocumentTileContainer(good2); + const sut2 = getDocumentTileContainer(poor2); + const sut3 = getDocumentTileContainer(poor3); + const sut4 = getDocumentTileContainer(poor4); + const sut5 = getDocumentTileContainer(poor5); + + const baseline = sut1.flatten().length; // should be 19 + expect(sut2.flatten().length).to.equal(baseline - 1); + expect(sut2.atoms[0].asTile.atoms[4].asTile.atoms.filter(p => p instanceof DclAttribute).length).to.equal(3); + + expect(sut3.flatten().length).to.equal(baseline - 1); + expect(sut3.atoms[0].asTile.atoms[4].asTile.atoms.filter(p => p instanceof DclAttribute).length).to.equal(1); + + expect(sut4.flatten().length).to.equal(baseline - 2); + expect(sut4.atoms[0].asTile.atoms[4].asTile.atoms.filter(p => p instanceof DclAttribute).length).to.equal(2); + + expect(sut5.flatten().length).to.equal(baseline - 3); + expect(sut5.atoms[0].asTile.atoms[4].asTile.atoms.filter(p => p instanceof DclAttribute).length).to.equal(3); + } + catch (err) { + assert.fail("At least one test case parsed incorrectly or aggregated unexpectedly"); + } + }); + +}); \ No newline at end of file diff --git a/extension/src/test/suite/parsing.shared.test.ts b/extension/src/test/suite/parsing.shared.test.ts index b5e15ed2..032e1041 100644 --- a/extension/src/test/suite/parsing.shared.test.ts +++ b/extension/src/test/suite/parsing.shared.test.ts @@ -12,8 +12,8 @@ suite("Parsing: Shared Tests", function () { suiteSetup(() => { try { - windowsDoc = ReadonlyDocument.createMemoryDocument('(defun someFunc ()\r\n\t(command ".line" pause pause)\r\n\t(princ)\r\n)', 'lsp'); - linuxDoc = ReadonlyDocument.createMemoryDocument('(defun someFunc ()\n\t(command ".line" pause pause)\n\t(princ)\n)', 'lsp'); + windowsDoc = ReadonlyDocument.createMemoryDocument('(defun someFunc ()\r\n\t(command ".line" pause pause)\r\n\t(princ)\r\n)', 'autolisp'); + linuxDoc = ReadonlyDocument.createMemoryDocument('(defun someFunc ()\n\t(command ".line" pause pause)\n\t(princ)\n)', 'autolisp'); } catch (error) { assert.fail("Failed to initialize shared suite data sources"); } diff --git a/extension/src/test/suite/services.documentServices.test.ts b/extension/src/test/suite/services.documentServices.test.ts index 954ecee5..c6759111 100644 --- a/extension/src/test/suite/services.documentServices.test.ts +++ b/extension/src/test/suite/services.documentServices.test.ts @@ -86,7 +86,7 @@ suite("Analysis Support: DocumentServices Tests", function () { test("hasUnverifiedGlobalizers() - Expect false using a dynamic lsp", function () { try { const malformed = '(defun doStuff()\n(command "line" pause pause)\n(princ))\n;some comment\n(princ)\n32)'; - const memDoc = ReadonlyDocument.createMemoryDocument(malformed, 'lsp'); + const memDoc = ReadonlyDocument.createMemoryDocument(malformed, 'autolisp'); const sut = DocumentServices.hasUnverifiedGlobalizers(memDoc); expect(sut).to.equal(false); }