Skip to content

Commit

Permalink
copy expectDiagnostics from brighterscript for tests (#95)
Browse files Browse the repository at this point in the history
* copy expectDiagnostics from brighterscript for tests

* Don't run double builds for PR pushes
  • Loading branch information
TwitchBronBron authored Sep 25, 2023
1 parent 3567a92 commit bba427b
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 52 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: build
on: [push, pull_request]
on:
push:
branches:
- master
tags:
- v*
pull_request:

jobs:
ci:
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"mocha": "^9.2.2",
"nyc": "^15.1.0",
"source-map-support": "^0.5.21",
"thenby": "^1.3.4",
"ts-node": "^10.7.0",
"typescript": "^4.9.4"
},
Expand Down
30 changes: 8 additions & 22 deletions src/plugins/checkUsage/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import { expect } from 'chai';
import { BsDiagnostic, Program } from 'brighterscript';
import { Program } from 'brighterscript';
import * as path from 'path';
import Linter from '../../Linter';
import { createContext, PluginWrapperContext } from '../../util';
import CheckUsage from './index';

function pad(n: number) {
return n > 9 ? `${n}` : `0${n}`;
}

function fmtDiagnostics(diagnostics: BsDiagnostic[]) {
return diagnostics
.filter((d) => d.severity && d.severity < 4)
.sort((a, b) => a.range.start.line - b.range.start.line)
.map((d) => `${pad(d.range.start.line + 1)}:${d.code}:${d.message}`.replace('\\', '/')); // Win to nix path
}
import { expectDiagnosticsFmt } from '../../testHelpers.spec';

describe('checkUsage', () => {
let linter: Linter;
Expand Down Expand Up @@ -44,9 +34,7 @@ describe('checkUsage', () => {
rules: {
}
});
const actual = fmtDiagnostics(diagnostics);
const expected = [];
expect(actual).deep.equal(expected);
expectDiagnosticsFmt(diagnostics, []);
});

it('detects component refered as child', async () => {
Expand All @@ -61,11 +49,9 @@ describe('checkUsage', () => {
rules: {
}
});
const actual = fmtDiagnostics(diagnostics);
const expected = [
`01:LINT4002:Script 'components/child2.brs' does not seem to be used`,
`02:LINT4001:Component 'components/child2.xml' does not seem to be used`
];
expect(actual).deep.equal(expected);
expectDiagnosticsFmt(diagnostics, [
`01:LINT4002:Script 'components${path.sep}child2.brs' does not seem to be used`,
`02:LINT4001:Component 'components${path.sep}child2.xml' does not seem to be used`
]);
});
});
14 changes: 2 additions & 12 deletions src/plugins/codeStyle/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import * as fs from 'fs';
import { expect } from 'chai';
import { AALiteralExpression, AssignmentStatement, BsDiagnostic, ParseMode, Parser, Program, util } from 'brighterscript';
import { AALiteralExpression, AssignmentStatement, ParseMode, Parser, Program, util } from 'brighterscript';
import Linter from '../../Linter';
import CodeStyle, { collectWrappingAAMembersIndexes } from './index';
import { createContext, PluginWrapperContext } from '../../util';

function pad(n: number) {
return n > 9 ? `${n}` : `0${n}`;
}

function fmtDiagnostics(diagnostics: BsDiagnostic[]) {
return diagnostics
.filter((d) => d.severity && d.severity < 4)
.sort((a, b) => a.range.start.line - b.range.start.line)
.map((d) => `${pad(d.range.start.line + 1)}:${d.code}:${d.message}`);
}
import { fmtDiagnostics } from '../../testHelpers.spec';

describe('codeStyle', () => {
let linter: Linter;
Expand Down
25 changes: 8 additions & 17 deletions src/plugins/trackCodeFlow/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import * as fs from 'fs';
import { expect } from 'chai';
import { BsDiagnostic, Program } from 'brighterscript';
import { Program, util } from 'brighterscript';
import Linter from '../../Linter';
import TrackCodeFlow from './index';
import bslintFactory from '../../index';
import { createContext, PluginWrapperContext } from '../../util';

function pad(n: number) {
return n > 9 ? `${n}` : `0${n}`;
}

function fmtDiagnostics(diagnostics: BsDiagnostic[]) {
return diagnostics
.filter((d) => d.severity && d.severity < 4)
.sort((a, b) => a.range.start.line - b.range.start.line)
.map((d) => `${pad(d.range.start.line + 1)}:${d.code}:${d.message}`);
}
import { expectDiagnostics, fmtDiagnostics } from '../../testHelpers.spec';
import { VarLintError } from './varTracking';

describe('trackCodeFlow', () => {
let linter: Linter;
Expand Down Expand Up @@ -56,11 +47,11 @@ describe('trackCodeFlow', () => {
`);
program.validate();

expect(
fmtDiagnostics(program.getDiagnostics())
).to.eql([
`10:LINT1003:Not all the code paths assign 'text2'`
]);
expectDiagnostics(program, [{
code: VarLintError.UnsafeInitialization,
message: `Not all the code paths assign 'text2'`,
range: util.createRange(9, 22, 9, 27)
}]);
});

it('detects use of uninitialized vars', async () => {
Expand Down
140 changes: 140 additions & 0 deletions src/testHelpers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { BsDiagnostic, BscFile, DiagnosticSeverity, DiagnosticTag, Range } from 'brighterscript';
import { CodeDescription, DiagnosticRelatedInformation, Diagnostic } from 'vscode-languageserver-types';
import { expect } from 'chai';
import { firstBy } from 'thenby';

type DiagnosticCollection = { getDiagnostics: () => Array<Diagnostic> } | { diagnostics: Diagnostic[] } | Diagnostic[];
function getDiagnostics(arg: DiagnosticCollection): BsDiagnostic[] {
if (Array.isArray(arg)) {
return arg as BsDiagnostic[];
} else if ((arg as any).getDiagnostics) {
return (arg as any).getDiagnostics();
} else if ((arg as any).diagnostics) {
return (arg as any).diagnostics;
} else {
throw new Error('Cannot derive a list of diagnostics from ' + JSON.stringify(arg));
}
}

function sortDiagnostics(diagnostics: BsDiagnostic[]) {
return diagnostics.sort(
firstBy<BsDiagnostic>('code')
.thenBy<BsDiagnostic>('message')
.thenBy<BsDiagnostic>((a, b) => (a.range?.start?.line ?? 0) - (b.range?.start?.line ?? 0))
.thenBy<BsDiagnostic>((a, b) => (a.range?.start?.character ?? 0) - (b.range?.start?.character ?? 0))
.thenBy<BsDiagnostic>((a, b) => (a.range?.end?.line ?? 0) - (b.range?.end?.line ?? 0))
.thenBy<BsDiagnostic>((a, b) => (a.range?.end?.character ?? 0) - (b.range?.end?.character ?? 0))
);
}

function cloneObject<TOriginal, TTemplate>(original: TOriginal, template: TTemplate, defaultKeys: Array<keyof TOriginal>) {
const clone = {} as Partial<TOriginal>;
let keys = Object.keys(template ?? {}) as Array<keyof TOriginal>;
// if there were no keys provided, use some sane defaults
keys = keys.length > 0 ? keys : defaultKeys;

// copy only compare the specified keys from actualDiagnostic
for (const key of keys) {
clone[key] = original[key];
}
return clone;
}

interface PartialDiagnostic {
range?: Range;
severity?: DiagnosticSeverity;
code?: number | string;
codeDescription?: Partial<CodeDescription>;
source?: string;
message?: string;
tags?: Partial<DiagnosticTag>[];
relatedInformation?: Partial<DiagnosticRelatedInformation>[];
data?: unknown;
file?: Partial<BscFile>;
}

/**
* Helper function to clone a Diagnostic so it will give partial data that has the same properties as the expected
*/
function cloneDiagnostic(actualDiagnosticInput: BsDiagnostic, expectedDiagnostic: BsDiagnostic) {
const actualDiagnostic = cloneObject(
actualDiagnosticInput,
expectedDiagnostic,
['message', 'code', 'range', 'severity', 'relatedInformation']
);
// deep clone relatedInformation if available
if (actualDiagnostic.relatedInformation) {
for (let j = 0; j < actualDiagnostic.relatedInformation.length; j++) {
actualDiagnostic.relatedInformation[j] = cloneObject(
actualDiagnostic.relatedInformation[j],
expectedDiagnostic?.relatedInformation[j],
['location', 'message']
) as any;
}
}
// deep clone file info if available
if (actualDiagnostic.file) {
actualDiagnostic.file = cloneObject(
actualDiagnostic.file,
expectedDiagnostic?.file,
['srcPath', 'pkgPath']
) as any;
}
return actualDiagnostic;
}


/**
* Ensure the DiagnosticCollection exactly contains the data from expected list.
* @param arg - any object that contains diagnostics (such as `Program`, `Scope`, or even an array of diagnostics)
* @param expected an array of expected diagnostics. if it's a string, assume that's a diagnostic error message
*/
export function expectDiagnostics(arg: DiagnosticCollection, expected: Array<PartialDiagnostic | string | number>) {
const actualDiagnostics = sortDiagnostics(
getDiagnostics(arg)
);
const expectedDiagnostics = sortDiagnostics(
expected.map(x => {
let result = x;
if (typeof x === 'string') {
result = { message: x };
} else if (typeof x === 'number') {
result = { code: x };
}
return result as unknown as BsDiagnostic;
})
);

const actual = [] as BsDiagnostic[];
for (let i = 0; i < actualDiagnostics.length; i++) {
const expectedDiagnostic = expectedDiagnostics[i];
const actualDiagnostic = cloneDiagnostic(actualDiagnostics[i], expectedDiagnostic);
actual.push(actualDiagnostic as any);
}
expect(actual).to.eql(expectedDiagnostics);
}


function pad(n: number) {
return n > 9 ? `${n}` : `0${n}`;
}

export function fmtDiagnostics(diagnostics: BsDiagnostic[]) {
return diagnostics
.filter((d) => d.severity && d.severity < 4)
.sort((a, b) => a.range.start.line - b.range.start.line)
.map((d) => `${pad(d.range.start.line + 1)}:${d.code}:${d.message}`);
}

/**
* Format a list of diagnostics and ensure they match the expectedDiagnostics string list
*/
export function expectDiagnosticsFmt(diagnosticCollection: DiagnosticCollection, expectedDiagnostics: string[]) {
const diagnostics = getDiagnostics(diagnosticCollection);
const formatted = fmtDiagnostics(diagnostics);
expect(
formatted
).eql(
expectedDiagnostics
);
}

0 comments on commit bba427b

Please sign in to comment.