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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/lexer/Lexer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { Token } from './Token';
import { isToken } from './Token';
import { rangeToArray } from '../parser/Parser.spec';
import { Range } from 'vscode-languageserver';
import util from '../util';
import { DiagnosticMessages } from '../DiagnosticMessages';

describe('lexer', () => {
Expand Down Expand Up @@ -1338,6 +1337,17 @@ describe('lexer', () => {
});

it('properly tracks leadingWhitespace', () => {
/**
* Convert a list of tokens into a string, including their leading whitespace
*/
function tokensToString(tokens: Token[]) {
let result = '';
//skip iterating the final token
for (let token of tokens) {
result += token.leadingWhitespace + token.text;
}
return result;
}
const text = `
sub main()

Expand All @@ -1346,7 +1356,7 @@ describe('lexer', () => {
end sub
`;
const { tokens } = Lexer.scan(text, { includeWhitespace: false });
expect(util.tokensToString(tokens)).to.equal(text);
expect(tokensToString(tokens)).to.equal(text);
});

it('properly detects try/catch tokens', () => {
Expand Down
132 changes: 105 additions & 27 deletions src/util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@ describe('util', () => {
]
};
util.resolvePathsRelativeTo(config, 'plugins', s`${rootDir}/config`);
expect(config?.plugins?.map(p => (p ? util.pathSepNormalize(p, '/') : undefined))).to.deep.equal([
expect(config?.plugins?.map(p => (p ? pathSepNormalize(p, '/') : undefined))).to.deep.equal([
`${rootDir}/config/plugins.js`,
`${rootDir}/config/scripts/plugins.js`,
`${rootDir}/scripts/plugins.js`,
'bsplugin'
].map(p => util.pathSepNormalize(p, '/')));
].map(p => pathSepNormalize(p, '/')));
});

it('resolves path relatively to config file', () => {
Expand Down Expand Up @@ -175,10 +175,10 @@ describe('util', () => {
]
};
util.resolvePathsRelativeTo(config, 'plugins', s`${process.cwd()}/config`);
expect(config?.plugins?.map(p => (p ? util.pathSepNormalize(p, '/') : undefined))).to.deep.equal([
expect(config?.plugins?.map(p => (p ? pathSepNormalize(p, '/') : undefined))).to.deep.equal([
s`${process.cwd()}/config/plugins.js`,
'bsplugin'
].map(p => util.pathSepNormalize(p, '/')));
].map(p => pathSepNormalize(p, '/')));
});
});

Expand Down Expand Up @@ -232,8 +232,8 @@ describe('util', () => {

describe('pathSepNormalize', () => {
it('works for both types of separators', () => {
expect(util.pathSepNormalize('c:/some\\path', '\\')).to.equal('c:\\some\\path');
expect(util.pathSepNormalize('c:/some\\path', '/')).to.equal('c:/some/path');
expect(pathSepNormalize('c:/some\\path', '\\')).to.equal('c:\\some\\path');
expect(pathSepNormalize('c:/some\\path', '/')).to.equal('c:/some/path');
});
it('does not throw when given `undefined`', () => {
expect(undefined).to.be.undefined;
Expand All @@ -250,6 +250,37 @@ describe('util', () => {
});

describe('findClosestConfigFile', () => {

/**
* Walks up the chain to find the closest bsconfig.json file
*/
async function findClosestConfigFile(currentPath: string): Promise<string | undefined> {
//make the path absolute
currentPath = path.resolve(
path.normalize(
currentPath
)
);

let previousPath: string | undefined;
//using ../ on the root of the drive results in the same file path, so that's how we know we reached the top
while (previousPath !== currentPath) {
previousPath = currentPath;

let bsPath = path.join(currentPath, 'bsconfig.json');
let brsPath = path.join(currentPath, 'brsconfig.json');
if (await util.pathExists(bsPath)) {
return bsPath;
} else if (await util.pathExists(brsPath)) {
return brsPath;
} else {
//walk upwards one directory
currentPath = path.resolve(path.join(currentPath, '../'));
}
}
//got to the root path, no config file exists
}

it('finds config up the chain', async () => {
const brsFilePath = s`${rootDir}/src/app.brs`;
const currentDirBsConfigPath = s`${rootDir}/src/bsconfig.json`;
Expand All @@ -262,13 +293,13 @@ describe('util', () => {
fsExtra.outputFileSync(parentDirBsConfigPath, '');
fsExtra.outputFileSync(parentDirBrsConfigPath, '');

expect(await util.findClosestConfigFile(brsFilePath)).to.equal(currentDirBsConfigPath);
expect(await findClosestConfigFile(brsFilePath)).to.equal(currentDirBsConfigPath);
fsExtra.removeSync(currentDirBsConfigPath);
expect(await util.findClosestConfigFile(brsFilePath)).to.equal(currentDirBrsConfigPath);
expect(await findClosestConfigFile(brsFilePath)).to.equal(currentDirBrsConfigPath);
fsExtra.removeSync(currentDirBrsConfigPath);
expect(await util.findClosestConfigFile(brsFilePath)).to.equal(parentDirBsConfigPath);
expect(await findClosestConfigFile(brsFilePath)).to.equal(parentDirBsConfigPath);
fsExtra.removeSync(parentDirBsConfigPath);
expect(await util.findClosestConfigFile(brsFilePath)).to.equal(parentDirBrsConfigPath);
expect(await findClosestConfigFile(brsFilePath)).to.equal(parentDirBrsConfigPath);
});

});
Expand Down Expand Up @@ -484,17 +515,33 @@ describe('util', () => {
});

describe('areArraysEqual', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably delete this one

/**
* Determine if two arrays containing primitive values are equal.
* This considers order and compares by equality.
*/
function areArraysEqual(arr1: any[], arr2: any[]) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}

it('finds equal arrays', () => {
expect(util.areArraysEqual([1, 2], [1, 2])).to.be.true;
expect(util.areArraysEqual(['cat', 'dog'], ['cat', 'dog'])).to.be.true;
expect(areArraysEqual([1, 2], [1, 2])).to.be.true;
expect(areArraysEqual(['cat', 'dog'], ['cat', 'dog'])).to.be.true;
});
it('detects non-equal arrays', () => {
expect(util.areArraysEqual([1, 2], [1])).to.be.false;
expect(util.areArraysEqual([1, 2], [2])).to.be.false;
expect(util.areArraysEqual([2], [1])).to.be.false;
expect(util.areArraysEqual([2], [0])).to.be.false;
expect(util.areArraysEqual(['cat', 'dog'], ['cat', 'dog', 'mouse'])).to.be.false;
expect(util.areArraysEqual(['cat', 'dog'], ['dog', 'cat'])).to.be.false;
expect(areArraysEqual([1, 2], [1])).to.be.false;
expect(areArraysEqual([1, 2], [2])).to.be.false;
expect(areArraysEqual([2], [1])).to.be.false;
expect(areArraysEqual([2], [0])).to.be.false;
expect(areArraysEqual(['cat', 'dog'], ['cat', 'dog', 'mouse'])).to.be.false;
expect(areArraysEqual(['cat', 'dog'], ['dog', 'cat'])).to.be.false;
});
});

Expand Down Expand Up @@ -548,12 +595,6 @@ describe('util', () => {
});
});

describe('padLeft', () => {
it('stops at an upper limit to prevent terrible memory explosions', () => {
expect(util.padLeft('', Number.MAX_VALUE, ' ')).to.be.lengthOf(1000);
});
});

describe('getTextForRange', () => {
const testArray = ['The quick', 'brown fox', 'jumps over', 'the lazy dog'];
const testString = testArray.join('\n');
Expand Down Expand Up @@ -955,9 +996,34 @@ describe('util', () => {
});

describe('splitWithLocation', () => {
/**
* Split the given text and return ranges for each chunk.
* Only works for single-line strings
*/
function splitGetRange(separator: string, text: string, range: Range) {
const chunks = text.split(separator);
const result = [] as Array<{ text: string; range: Range }>;
let offset = 0;
for (let chunk of chunks) {
//only keep nonzero chunks
if (chunk.length > 0) {
result.push({
text: chunk,
range: util.createRange(
range.start.line,
range.start.character + offset,
range.end.line,
range.start.character + offset + chunk.length
)
});
}
offset += chunk.length + separator.length;
}
return result;
}
it('works with no split items', () => {
expect(
util.splitGetRange('.', 'hello', util.createRange(2, 10, 2, 15))
splitGetRange('.', 'hello', util.createRange(2, 10, 2, 15))
).to.eql([{
text: 'hello',
range: util.createRange(2, 10, 2, 15)
Expand All @@ -966,7 +1032,7 @@ describe('util', () => {

it('handles empty chunks', () => {
expect(
util.splitGetRange('l', 'hello', util.createRange(2, 10, 2, 15))
splitGetRange('l', 'hello', util.createRange(2, 10, 2, 15))
).to.eql([{
text: 'he',
range: util.createRange(2, 10, 2, 12)
Expand All @@ -978,7 +1044,7 @@ describe('util', () => {

it('handles multiple non-empty chunks', () => {
expect(
util.splitGetRange('.', 'abc.d.efgh.i', util.createRange(2, 10, 2, 2))
splitGetRange('.', 'abc.d.efgh.i', util.createRange(2, 10, 2, 2))
).to.eql([{
text: 'abc',
range: util.createRange(2, 10, 2, 13)
Expand Down Expand Up @@ -1698,3 +1764,15 @@ describe('util', () => {
});
});
});


/**
* Given a path to a file/directory, replace all path separators with the current system's version.
*/
function pathSepNormalize(filePath: string, separator?: string) {
if (!filePath) {
return filePath;
}
separator = separator ? separator : path.sep;
return filePath.replace(/[\\/]+/g, separator);
}
Loading
Loading