Filter panel results by project and hide test code

Roadmap item 3. One Filters button opens a menu holding both, rather than
two more controls in a toolbar that is already crowded in a docked panel:

- Hide test code, with the count of test results.
- A checkbox per project with its result count, plus All / None.

The button shows how many filters are active and the summary line reads
"12 of 43 references" while anything is filtered. Projects are persisted as
an *exclusion* list, so a project that only appears in a later search shows
up instead of being silently hidden. Test hiding seeds from
coloredReferences.hideTests on first results, then follows the panel.

What counts as test code is coloredReferences.testPattern, a regular
expression matched against the workspace-relative path and against the
containing project name. Matching on names turned out to need care: the
first pattern classified Latest.cs as a test because "Latest" contains
"test". The built-in pattern now requires the name to start at a boundary,
or an uppercase T for the CamelCase FooTests.cs form, and covers test/tests
directories, Tests.cs, foo_test.go, test_foo.py, foo.spec.ts and projects
named Something.Tests. An invalid configured regex is reported once and
ignored rather than throwing per search.

These filters narrow the panel only; the editor view still lists everything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
max
2026-09-07 17:50:02 +02:00
co-authored by Claude Opus 5
parent 3e8ece8ed7
commit 3df78ba490
8 changed files with 468 additions and 6 deletions
+19 -3
View File
@@ -11,8 +11,8 @@ highlighted like search matches; **writes are bold** on the theme's stronger wri
same colour the editor itself uses for a write.
**Panel view** shows the same results as a table with resizable, sortable columns — Code, File, Line, Kind,
Project and Containing member — grouped by file, with a filter box and a reads/writes filter. By default it
docks in the **bottom panel**
Project and Containing member — grouped by file, with a text filter plus filters for reads/writes, project and
test code. By default it docks in the **bottom panel**
alongside Terminal and Problems, where a wide, short table reads best; you can drag it to either side bar,
or set `coloredReferences.panelLocation` to put it in an editor group instead.
@@ -36,6 +36,19 @@ The **All / Reads / Writes** buttons filter by kind, and the summary line counts
the text filter, so typing `write` narrows to writes as well. See *Known limitations* for where the kind comes
from.
**Filters** opens a menu to hide test code or to include only some projects, each with its result count. The
button shows how many filters are active, and the summary line reads *"12 of 43 references"* while anything is
filtered. Choices persist per panel, and projects are remembered as an *exclusion* list — a project that only
turns up in a later search shows up rather than being silently hidden.
What counts as test code is a regular expression, `coloredReferences.testPattern`, matched against each
result's workspace-relative path and against its containing project name. The built-in pattern covers
`test`/`tests` directories, `FooTests.cs`, `Tests.cs`, `foo_test.go`, `test_foo.py`, `foo.spec.ts` and projects
named `Something.Tests` — without catching names that merely contain the word, like `Latest.cs` or a `contest`
directory. `coloredReferences.hideTests` sets whether the filter starts on.
These filters narrow what the panel shows; the editor view always lists every reference.
## Semantic tokens
VS Code colors code twice: a TextMate grammar handles keywords, strings and comments from the text alone, then
@@ -63,6 +76,9 @@ grammar-only colouring; results spanning more than 40 files skip it.
- `coloredReferences.openBeside` — open results beside the current editor (default `true`)
- `coloredReferences.showProject` — show the containing project in file headers (default `true`)
- `coloredReferences.reuseTab` — reuse one results tab/panel instead of opening a new one per search (default `true`)
- `coloredReferences.hideTests` — start the panel with test code hidden (default `false`)
- `coloredReferences.testPattern` — regular expression deciding what counts as test code; empty uses the
built-in pattern
## Install
@@ -107,6 +123,6 @@ the expected results stay deterministic.
1. ~~Webview panel with resizable, sortable columns~~ — done; missing: virtualized rendering for very large
result sets, and remembering column layout per workspace rather than per panel
2. ~~Semantic token overlay~~ — done in both views
3. Filter by project / exclude tests
3. ~~Filter by project / exclude tests~~ — done in the panel
4. ~~Read/write kind~~ — done in both views
+95
View File
@@ -345,3 +345,98 @@ body.resizing {
background: var(--vscode-inputValidation-warningBackground);
border-color: var(--vscode-inputValidation-warningBorder, transparent);
}
/* --- filters popover ---------------------------------------------------- */
.popover-host {
position: relative;
flex: 0 0 auto;
}
.popover {
position: absolute;
top: calc(100% + 4px);
right: 0;
z-index: 10;
min-width: 200px;
max-width: 320px;
max-height: 60vh;
overflow-y: auto;
padding: 4px;
color: var(--vscode-menu-foreground, var(--vscode-foreground));
background: var(--vscode-menu-background, var(--vscode-editorWidget-background));
border: 1px solid var(--vscode-menu-border, var(--vscode-editorWidget-border));
border-radius: 4px;
box-shadow: 0 2px 8px var(--vscode-widget-shadow, rgba(0, 0, 0, 0.36));
}
.popover[hidden] {
display: none;
}
.popover-heading {
padding: 6px 6px 2px;
color: var(--vscode-descriptionForeground);
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.popover-row {
display: flex;
align-items: center;
gap: 6px;
padding: 3px 6px;
border-radius: 2px;
cursor: pointer;
white-space: nowrap;
}
.popover-row:hover {
background: var(--vscode-list-hoverBackground);
}
.popover-row.disabled {
opacity: 0.5;
}
.popover-row input {
flex: 0 0 auto;
margin: 0;
accent-color: var(--vscode-checkbox-background, var(--vscode-button-background));
}
.popover-label {
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
}
.popover-count {
flex: 0 0 auto;
color: var(--vscode-descriptionForeground);
font-variant-numeric: tabular-nums;
}
.popover-actions {
display: flex;
gap: 8px;
padding: 4px 6px 2px;
border-top: 1px solid var(--vscode-menu-separatorBackground, var(--vscode-panel-border));
margin-top: 4px;
}
.link-button {
padding: 0;
color: var(--vscode-textLink-foreground);
background: none;
border: none;
font-family: inherit;
font-size: inherit;
cursor: pointer;
}
.link-button:hover {
color: var(--vscode-textLink-activeForeground);
text-decoration: underline;
}
+149 -1
View File
@@ -28,7 +28,9 @@
{ value: 'write', label: 'Writes', title: 'Show only references that write the symbol' },
];
/** @type {{symbol: string, languageId: string, rows: any[], fileCount: number}} */
/** @type {{symbol: string, languageId: string, rows: any[], fileCount: number,
* projects?: {name: string, count: number}[], testCount?: number,
* hideTestsDefault?: boolean}} */
let data = { symbol: '', languageId: '', rows: [], fileCount: 0 };
const restored = vscode.getState() || {};
@@ -38,6 +40,11 @@
group: restored.group !== false,
filter: restored.filter || '',
kind: KIND_FILTERS.some(k => k.value === restored.kind) ? restored.kind : 'all',
// Projects are stored as an *exclusion* list, so a project that only turns up in
// a later search is visible by default rather than silently filtered out.
hiddenProjects: Array.isArray(restored.hiddenProjects) ? restored.hiddenProjects : [],
// undefined until the first results arrive, then seeded from the setting.
hideTests: typeof restored.hideTests === 'boolean' ? restored.hideTests : undefined,
widths: Object.assign({}, restored.widths),
collapsed: Object.assign({}, restored.collapsed),
selectedId: restored.selectedId,
@@ -47,6 +54,8 @@
summary: /** @type {HTMLElement} */ (document.getElementById('summary')),
filter: /** @type {HTMLInputElement} */ (document.getElementById('filter')),
kindFilter: /** @type {HTMLElement} */ (document.getElementById('kind-filter')),
filters: /** @type {HTMLButtonElement} */ (document.getElementById('filters')),
filtersMenu: /** @type {HTMLElement} */ (document.getElementById('filters-menu')),
group: /** @type {HTMLButtonElement} */ (document.getElementById('toggle-group')),
refresh: /** @type {HTMLButtonElement} */ (document.getElementById('refresh')),
table: /** @type {HTMLElement} */ (document.getElementById('table')),
@@ -404,6 +413,12 @@
if (state.kind !== 'all' && row.kind !== state.kind) {
return false;
}
if (state.hideTests && row.isTest) {
return false;
}
if (state.hiddenProjects.indexOf(row.project) >= 0) {
return false;
}
if (!needle) {
return true;
}
@@ -638,6 +653,128 @@
renderRows();
});
// -----------------------------------------------------------------------
// Project / test filters
// -----------------------------------------------------------------------
function activeFilterCount() {
return state.hiddenProjects.filter(
p => (data.projects || []).some(entry => entry.name === p)).length +
(state.hideTests ? 1 : 0);
}
function refreshFiltersButton() {
const active = activeFilterCount();
el.filters.textContent = active > 0 ? `Filters (${active})` : 'Filters';
el.filters.setAttribute('aria-pressed', String(active > 0));
}
function checkboxRow(label, checked, onToggle, count) {
const row = document.createElement('label');
row.className = 'popover-row';
const box = document.createElement('input');
box.type = 'checkbox';
box.checked = checked;
box.addEventListener('change', () => onToggle(box.checked));
row.appendChild(box);
const text = document.createElement('span');
text.className = 'popover-label';
text.textContent = label;
row.appendChild(text);
if (count !== undefined) {
const badge = document.createElement('span');
badge.className = 'popover-count';
badge.textContent = String(count);
row.appendChild(badge);
}
return row;
}
function buildFiltersMenu() {
const menu = el.filtersMenu;
menu.textContent = '';
const tests = data.testCount || 0;
const testRow = checkboxRow('Hide test code', !!state.hideTests, checked => {
state.hideTests = checked;
saveState();
buildFiltersMenu();
refreshFiltersButton();
renderRows();
}, tests);
if (tests === 0) {
testRow.classList.add('disabled');
testRow.title = 'No results are in test code';
}
menu.appendChild(testRow);
const projects = data.projects || [];
if (projects.length > 1) {
const heading = document.createElement('div');
heading.className = 'popover-heading';
heading.textContent = 'Projects';
menu.appendChild(heading);
for (const project of projects) {
const shown = state.hiddenProjects.indexOf(project.name) < 0;
menu.appendChild(checkboxRow(project.name || '(no project)', shown, checked => {
state.hiddenProjects = state.hiddenProjects.filter(p => p !== project.name);
if (!checked) {
state.hiddenProjects.push(project.name);
}
saveState();
buildFiltersMenu();
refreshFiltersButton();
renderRows();
}, project.count));
}
const actions = document.createElement('div');
actions.className = 'popover-actions';
for (const [label, hidden] of [['All', []], ['None', projects.map(p => p.name)]]) {
const button = document.createElement('button');
button.className = 'link-button';
button.textContent = /** @type {string} */ (label);
button.addEventListener('click', () => {
state.hiddenProjects = /** @type {string[]} */ (hidden).slice();
saveState();
buildFiltersMenu();
refreshFiltersButton();
renderRows();
});
actions.appendChild(button);
}
menu.appendChild(actions);
}
}
function openFilters(open) {
el.filtersMenu.hidden = !open;
el.filters.setAttribute('aria-expanded', String(open));
if (open) {
buildFiltersMenu();
const first = el.filtersMenu.querySelector('input');
if (first) {
/** @type {HTMLElement} */ (first).focus();
}
}
}
el.filters.addEventListener('click', event => {
event.stopPropagation();
openFilters(el.filtersMenu.hidden);
});
document.addEventListener('click', event => {
const target = /** @type {HTMLElement} */ (event.target);
if (!el.filtersMenu.hidden && !el.filtersMenu.contains(target) && target !== el.filters) {
openFilters(false);
}
});
function buildKindFilter() {
el.kindFilter.textContent = '';
for (const option of KIND_FILTERS) {
@@ -671,6 +808,9 @@
event.preventDefault();
el.filter.focus();
el.filter.select();
} else if (event.key === 'Escape' && !el.filtersMenu.hidden) {
openFilters(false);
el.filters.focus();
} else if (event.key === 'Escape' && document.activeElement === el.filter) {
el.filter.value = '';
state.filter = '';
@@ -692,6 +832,13 @@
if (message.type === 'results') {
const newSymbol = message.symbol !== data.symbol;
data = message;
if (typeof state.hideTests !== 'boolean') {
state.hideTests = !!data.hideTestsDefault;
}
refreshFiltersButton();
if (!el.filtersMenu.hidden) {
buildFiltersMenu();
}
// Row ids are positional, so a selection carried over from another symbol
// would point at an unrelated reference.
if (newSymbol || !data.rows.some(r => r.id === state.selectedId)) {
@@ -716,6 +863,7 @@
applyPalette();
el.filter.value = state.filter;
buildKindFilter();
refreshFiltersButton();
el.group.setAttribute('aria-pressed', String(state.group));
el.body.tabIndex = 0;
computeAutoWidths();
+10
View File
@@ -165,6 +165,16 @@
"type": "boolean",
"default": true,
"description": "Use semantic tokens from the language server so identifiers are coloured by what they are. The editor view gets the theme-exact colouring; the panel gets the classification with an approximated palette. Turn off for grammar-only colouring."
},
"coloredReferences.hideTests": {
"type": "boolean",
"default": false,
"description": "Hide references in test code by default. The panel's Filters button toggles it per search."
},
"coloredReferences.testPattern": {
"type": "string",
"default": "",
"markdownDescription": "Regular expression deciding what counts as test code, matched against each result's workspace-relative path and against its containing project name. Leave empty for the built-in pattern, which covers `test`/`tests` directories, `*Tests.cs`, `*_test.go`, `*.spec.ts` and projects named `*.Tests`. Set it to a pattern that matches nothing (for example `$^`) to treat everything as production code."
}
}
},
+26 -2
View File
@@ -1,7 +1,8 @@
import * as vscode from 'vscode';
import * as path from 'path';
import {
RefKind, ReferenceResults, applyKinds, containingMembers, displayLine, referenceKinds, relativePath,
RefKind, ReferenceResults, applyKinds, containingMembers, displayLine, isTest, referenceKinds,
relativePath, testPattern,
} from './references';
import { SEMANTIC_FILE_LIMIT, sourceLegend, sourceTokens } from './semantic';
@@ -80,6 +81,8 @@ export interface PanelRow {
kind: RefKind;
/** Semantic token spans within `code`, empty when the server serves none. */
spans: CodeSpan[];
/** Whether this reference lives in test code. */
isTest: boolean;
}
/**
@@ -97,9 +100,12 @@ export function buildRows(
const rows: PanelRow[] = [];
const locations = new Map<number, vscode.Location>();
const pattern = testPattern();
let id = 0;
for (const file of results.files) {
const relative = relativePath(file.uri);
const test = isTest(file.uri, file.project, pattern);
const dir = path.dirname(relative);
for (const source of file.lines) {
const { text, symbolRanges } = displayLine(source);
@@ -119,6 +125,7 @@ export function buildRows(
member,
kind: symbolRanges[index].kind,
spans: spans.get(`${file.uri.toString()}|${source.line}`) ?? [],
isTest: test,
});
locations.set(id, new vscode.Location(file.uri, range.start));
id++;
@@ -198,12 +205,23 @@ export class ResultsView {
const { rows, locations } = buildRows(results, members, kinds, spans);
this.locations = locations;
// Projects in result order, with counts, so the filter list needs no work
// in the webview.
const projects = new Map<string, number>();
for (const row of rows) {
projects.set(row.project, (projects.get(row.project) ?? 0) + 1);
}
this.post({
type: 'results',
symbol: results.symbol,
languageId: results.languageId,
fileCount: results.files.length,
writeCount: rows.filter(r => r.kind === 'write').length,
testCount: rows.filter(r => r.isTest).length,
projects: [...projects].map(([name, count]) => ({ name, count })),
hideTestsDefault: vscode.workspace.getConfiguration('coloredReferences')
.get<boolean>('hideTests', false),
rows,
});
}
@@ -293,8 +311,14 @@ export class ResultsView {
<span id="summary"></span>
<input id="filter" type="text" placeholder="Filter results (Ctrl+F)" spellcheck="false">
<div id="kind-filter" class="segmented" role="group" aria-label="Filter by read or write"></div>
<div class="popover-host">
<button id="filters" class="toolbar-button" aria-expanded="false"
aria-haspopup="true" title="Filter by project, or hide test code">Filters</button>
<div id="filters-menu" class="popover" role="dialog"
aria-label="Project and test filters" hidden></div>
</div>
<button id="toggle-group" class="toolbar-button" aria-pressed="true"
title="Group results by file">Group by file</button>
title="Group results by file">Group</button>
<button id="refresh" class="toolbar-button" title="Re-run the search (F5)">Refresh</button>
</div>
<div id="table">
+63
View File
@@ -402,6 +402,69 @@ export function invalidateSymbols(uri: vscode.Uri): void {
symbolCache.delete(uri.toString());
}
// ---------------------------------------------------------------------------
// Test code
// ---------------------------------------------------------------------------
/**
* Matches test projects and test files by the usual conventions: a `test`/`tests`
* directory, a project or file named `…Test`/`…Tests`, Go's `_test.go`, and
* `.spec.` / `.test.` files.
*/
const TEST_EXTENSIONS = '(?:cs|fs|vb|ts|tsx|js|jsx|py|go|rs|java|kt)';
export const DEFAULT_TEST_PATTERN = [
// a test / tests directory anywhere in the path
'(?:^|[\\\\/])[Tt]ests?[\\\\/]',
// Tests.cs, foo_test.go, foo.test.ts — the name must *start* at a boundary, so
// "Latest.cs" and "Attestation.cs" are not caught by the "test" inside them
`(?:^|[\\\\/_.\\-])[Tt]ests?\\.${TEST_EXTENSIONS}$`,
// FooTests.cs — CamelCase, so only an uppercase T counts here
`[A-Za-z0-9]Tests?\\.${TEST_EXTENSIONS}$`,
// test_foo.py — pytest's default prefix convention
'(?:^|[\\\\/])[Tt]est_',
// foo.spec.ts
'\\.spec\\.(?:ts|tsx|js|jsx)$',
// a project named Tests or Something.Tests
'(?:^|\\.)[Tt]ests?$',
].join('|');
let cachedPattern: { source: string; regex: RegExp | undefined } | undefined;
/** The configured test pattern, compiled once. Invalid regexes are reported and ignored. */
export function testPattern(): RegExp | undefined {
const configured = vscode.workspace.getConfiguration('coloredReferences')
.get<string>('testPattern', '') ?? '';
// An unset setting means "use the built-in pattern"; to treat everything as
// production code, configure a pattern that cannot match.
const source = configured.trim().length > 0 ? configured : DEFAULT_TEST_PATTERN;
if (cachedPattern?.source === source) {
return cachedPattern.regex;
}
let regex: RegExp | undefined;
try {
regex = new RegExp(source);
} catch (error) {
void vscode.window.showWarningMessage(
`Colored References: coloredReferences.testPattern is not a valid regular expression (${error}).`);
}
cachedPattern = { source, regex };
return regex;
}
/** Whether a reference lives in test code, by path or by containing project name. */
export function isTest(
uri: vscode.Uri, project: string | undefined, pattern = testPattern(),
): boolean {
if (!pattern) {
return false;
}
// Test both separators so one pattern works on either platform.
const relative = relativePath(uri);
return pattern.test(relative) || pattern.test(relative.replace(/\//g, '\\')) ||
(project !== undefined && pattern.test(project));
}
// ---------------------------------------------------------------------------
// Read / write kind
// ---------------------------------------------------------------------------
+105
View File
@@ -0,0 +1,105 @@
import * as assert from 'assert';
import * as path from 'path';
import * as vscode from 'vscode';
import { buildRows } from '../../panel';
import { DEFAULT_TEST_PATTERN, gather, isTest, testPattern } from '../../references';
const TARGET_FILE = path.join('Nerfed.Runtime', 'Profiler.cs');
const TARGET_SYMBOL = 'Profiler';
const TARGET_DECL = 'public static class Profiler';
const builtIn = new RegExp(DEFAULT_TEST_PATTERN);
/** isTest against the built-in pattern, without touching configuration. */
function classify(relativePath: string, project?: string): boolean {
// isTest() resolves the path through asRelativePath, so feed it an absolute path
// inside the workspace and let it come back out relative.
const folder = vscode.workspace.workspaceFolders?.[0];
assert.ok(folder, 'no workspace folder');
const uri = vscode.Uri.joinPath(folder.uri, ...relativePath.split('/'));
return isTest(uri, project, builtIn);
}
suite('Test-code detection', () => {
test('the built-in pattern matches the usual conventions', () => {
assert.ok(classify('tests/Foo.cs'), 'tests/ directory');
assert.ok(classify('Nerfed.Runtime/Tests/Foo.cs'), 'nested Tests/ directory');
assert.ok(classify('src/FooTests.cs'), 'FooTests.cs');
assert.ok(classify('src/FooTest.cs'), 'FooTest.cs');
assert.ok(classify('pkg/thing_test.go'), 'Go test file');
assert.ok(classify('src/thing.spec.ts'), 'spec.ts');
assert.ok(classify('src/thing.test.ts'), 'test.ts');
assert.ok(classify('src/test_thing.py'), 'pytest prefix');
});
test('a project named after tests counts even when the path does not', () => {
assert.ok(classify('src/Widget.cs', 'Nerfed.Tests'), 'project Nerfed.Tests');
assert.ok(classify('src/Widget.cs', 'Tests'), 'project Tests');
assert.ok(!classify('src/Widget.cs', 'Nerfed.Runtime'), 'production project');
assert.ok(!classify('src/Widget.cs', 'Latest'), 'project ending in "test"');
assert.ok(!classify('src/Widget.cs', 'Contest'), 'project containing "test"');
});
test('production code is not mistaken for test code', () => {
assert.ok(!classify('Nerfed.Runtime/Profiler.cs'));
assert.ok(!classify('Nerfed.Runtime/Systems/LocalToWorldSystem.cs'));
// These all contain "test" without being test code — the boundary rules matter.
assert.ok(!classify('src/Latest.cs'), 'Latest.cs');
assert.ok(!classify('src/Protest.cs'), 'Protest.cs');
assert.ok(!classify('src/greatest.ts'), 'greatest.ts');
assert.ok(!classify('src/contest/Entry.cs'), 'contest/ directory');
assert.ok(!classify('src/Attestation.cs'), 'Attestation.cs');
});
test('a configured pattern that cannot match disables detection', () => {
assert.ok(!isTest(vscode.Uri.file('/repo/tests/Foo.cs'), 'Tests', new RegExp('$^')));
});
test('the default configuration resolves to the built-in pattern', () => {
const pattern = testPattern();
assert.ok(pattern, 'no pattern compiled');
assert.strictEqual(pattern.source, builtIn.source);
});
});
suite('Project and test filtering data', () => {
test('rows expose the project and a test flag the panel can filter on', async () => {
const ext = vscode.extensions.getExtension('local.colored-references');
assert.ok(ext);
await ext.activate();
const folder = vscode.workspace.workspaceFolders?.[0];
assert.ok(folder);
const uri = vscode.Uri.joinPath(folder.uri, ...TARGET_FILE.split(path.sep));
const doc = await vscode.workspace.openTextDocument(uri);
let position = new vscode.Position(0, 0);
for (let i = 0; i < doc.lineCount; i++) {
const at = doc.lineAt(i).text.indexOf(TARGET_DECL);
if (at >= 0) {
position = new vscode.Position(i, doc.lineAt(i).text.indexOf(TARGET_SYMBOL, at) + 1);
break;
}
}
const locations = await vscode.commands.executeCommand<vscode.Location[]>(
'vscode.executeReferenceProvider', uri, position);
assert.ok(locations && locations.length > 5);
const results = await gather(
TARGET_SYMBOL, 'csharp', { uri, position }, locations, true);
const { rows } = buildRows(results, new Map());
assert.ok(rows.length > 0);
// The Nerfed solution has no test projects, so nothing should be flagged —
// this is the false-positive check against real paths.
const flagged = rows.filter(r => r.isTest).map(r => r.relPath);
assert.deepStrictEqual([...new Set(flagged)], [],
`flagged as test code: ${[...new Set(flagged)].join(', ')}`);
const projects = [...new Set(rows.map(r => r.project))].sort();
console.log(`[test] projects in results: ${projects.join(', ')}`);
assert.ok(projects.length > 1, 'expected references in more than one project');
assert.ok(projects.every(p => p.length > 0), 'every row should name a project');
});
});
+1
View File
@@ -14,6 +14,7 @@ export function run(): Promise<void> {
mocha.addFile(path.resolve(__dirname, 'panel.test.js'));
mocha.addFile(path.resolve(__dirname, 'kinds.test.js'));
mocha.addFile(path.resolve(__dirname, 'semantic.test.js'));
mocha.addFile(path.resolve(__dirname, 'filters.test.js'));
return new Promise((resolve, reject) => {
try {