-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding utils to read and extract source fragments (#531)
- Loading branch information
Showing
5 changed files
with
183 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { existsSync, readFileSync } from 'fs'; | ||
import { Range } from './types'; | ||
|
||
const LINES_PATTERN = /(.*?(?:\r\n?|\n|$))/gm; | ||
export const _sourceCache = new Map<string, string>(); | ||
|
||
/** | ||
* Converts node positional numbers into a Range object. | ||
* | ||
* @param line - The source start line. | ||
* @param column - The source start column. | ||
* @param endLine - The source end line. | ||
* @param endColumn - The source end column. | ||
* @returns A range object. | ||
*/ | ||
export function buildRange( | ||
line: number, | ||
column: number, | ||
endLine?: number, | ||
endColumn?: number | ||
): Range { | ||
return { | ||
start: { | ||
line: line, | ||
column: column, | ||
}, | ||
end: { | ||
line: endLine ?? line, | ||
column: endColumn ?? column, | ||
}, | ||
}; | ||
} | ||
|
||
/** | ||
* Reads a source file, optionally caching it if it's already been read. | ||
* | ||
* @param filePath - The path to the source file. | ||
* @returns The file contents. | ||
*/ | ||
export function readSource(filePath: string | undefined): string { | ||
if (!filePath) { | ||
return ''; | ||
} | ||
|
||
if (existsSync(filePath) && !_sourceCache.has(filePath)) { | ||
const source = readFileSync(filePath, { encoding: 'utf8' }); | ||
|
||
_sourceCache.set(filePath, source); | ||
} | ||
|
||
return _sourceCache.get(filePath) || ''; | ||
} | ||
|
||
/** | ||
* Extracts a source fragment from a file's contents based on the provided Range. | ||
* | ||
* @param source - The file contents. | ||
* @param range - A Range object representing the range to extract from the file contents. | ||
* @returns The source fragment. | ||
*/ | ||
export function getSourceForRange(source: string, range: Range): string { | ||
if (!source) { | ||
return ''; | ||
} | ||
|
||
const sourceLines = source.match(LINES_PATTERN) || []; | ||
const firstLine = range.start.line - 1; | ||
const lastLine = range.end.line - 1; | ||
let currentLine = firstLine - 1; | ||
const firstColumn = range.start.column - 1; | ||
const lastColumn = range.end.column - 1; | ||
const src = []; | ||
let line; | ||
|
||
while (currentLine < lastLine) { | ||
currentLine++; | ||
line = sourceLines[currentLine]; | ||
|
||
if (currentLine === firstLine) { | ||
if (firstLine === lastLine) { | ||
src.push(line.slice(firstColumn, lastColumn)); | ||
} else { | ||
src.push(line.slice(firstColumn)); | ||
} | ||
} else if (currentLine === lastLine) { | ||
src.push(line.slice(0, lastColumn)); | ||
} else { | ||
src.push(line); | ||
} | ||
} | ||
|
||
return src.join(''); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { describe, it, expect } from 'vitest'; | ||
import { buildRange, getSourceForRange } from '../src/source'; | ||
|
||
describe('source', () => { | ||
describe('getSourceForRange', () => { | ||
it('returns empty string for empty source', () => { | ||
expect(getSourceForRange('', buildRange(0, 0))).toEqual(''); | ||
}); | ||
|
||
it('returns correct full single line fragment', () => { | ||
const source = "const someLongVariableDeclaration = 'This is a message!!'"; | ||
const range = buildRange(1, 1, 1, 58); | ||
|
||
expect(getSourceForRange(source, range)).toEqual(source); | ||
}); | ||
|
||
it('returns correct single line sub-fragment', () => { | ||
const source = "const someLongVariableDeclaration = 'This is a message!!'"; | ||
const range = buildRange(1, 1, 1, 34); | ||
|
||
expect(getSourceForRange(source, range)).toEqual('const someLongVariableDeclaration'); | ||
}); | ||
|
||
it('returns correct multi line sub-fragment', () => { | ||
const source = `function addOne(i) { | ||
if (i != NaN) { | ||
return i++; | ||
} | ||
return; | ||
}`; | ||
|
||
expect(getSourceForRange(source, buildRange(1, 10, 1, 16))).toEqual('addOne'); | ||
expect(getSourceForRange(source, buildRange(2, 7, 2, 15))).toEqual('i != NaN'); | ||
}); | ||
}); | ||
}); |