@anysphere/scip-typescript
Version:
SCIP indexer for TypeScript and JavaScript
193 lines (192 loc) • 7.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatSnapshot = void 0;
const Range_1 = require("./Range");
const scip_1 = require("./scip");
const stripIndexerPrefix = 'scip-typescript npm ';
const commentSyntax = '//';
const formatOptionsPrefix = '// format-options:';
function getSymbolTable(doc) {
const symbolTable = new Map();
for (const symbol of doc.symbols) {
symbolTable.set(symbol.symbol, symbol);
}
return symbolTable;
}
function parseOptions(lines) {
const formatOptions = {
showDocs: false,
showRanges: false,
};
for (const line of lines) {
if (!line.startsWith(formatOptionsPrefix)) {
continue;
}
const options = line.slice(formatOptionsPrefix.length).trim().split(',');
for (const option of options) {
const optionName = option.trim();
if (!(optionName in formatOptions)) {
throw new Error(`Invalid format option: ${optionName}`);
}
formatOptions[optionName] = true;
}
break;
}
return formatOptions;
}
function symbolNameForSnapshot(fullName) {
return fullName.startsWith(stripIndexerPrefix)
? fullName.slice(stripIndexerPrefix.length)
: fullName;
}
function formatSnapshot(input, doc, externalSymbols = []) {
const out = [];
const symbolTable = getSymbolTable(doc);
const externalSymbolTable = new Map();
for (const externalSymbol of externalSymbols) {
externalSymbolTable.set(externalSymbol.symbol, externalSymbol);
}
const symbolsWithDefinitions = new Set();
const formatOptions = parseOptions(input.lines);
for (const occurrence of doc.occurrences) {
const isDefinition = (occurrence.symbol_roles & scip_1.scip.SymbolRole.Definition) > 0;
if (isDefinition) {
symbolsWithDefinitions.add(occurrence.symbol);
}
}
const emittedDocstrings = new Set();
const pushDoc = (range, symbol, isDefinition, isStartOfLine) => {
// Only emit docstrings once
if (emittedDocstrings.has(symbol)) {
out.push('\n');
return;
}
// Only definitions OR symbols without a definition should be emitted
if (!isDefinition && symbolsWithDefinitions.has(symbol)) {
out.push('\n');
return;
}
emittedDocstrings.add(symbol);
let prefix = '\n' + commentSyntax;
if (!isStartOfLine) {
prefix += ' '.repeat(range.start.character - 2);
}
const pushOneDoc = (docs, external) => {
if (!formatOptions.showDocs) {
return;
}
for (const documentation of docs) {
for (const [idx, line] of documentation.split('\n').entries()) {
out.push(prefix);
if (idx === 0) {
if (external) {
out.push('external ');
}
out.push('documentation ');
}
else {
out.push(' > ');
}
out.push(line.slice(0, 40));
if (line.length > 40) {
out.push('...');
}
}
}
};
const pushOneRelationship = (relationships) => {
relationships.sort((a, b) => a.symbol.localeCompare(b.symbol));
for (const relationship of relationships) {
out.push(prefix);
out.push('relationship');
if (relationship.is_implementation) {
out.push(' implementation');
}
if (relationship.is_reference) {
out.push(' reference');
}
if (relationship.is_type_definition) {
out.push(' type_definition');
}
out.push(' ' + symbolNameForSnapshot(relationship.symbol));
}
};
const externalSymbol = externalSymbolTable.get(symbol);
if (externalSymbol) {
pushOneDoc(externalSymbol.documentation, true);
pushOneRelationship(externalSymbol.relationships);
}
else {
const info = symbolTable.get(symbol);
if (info) {
pushOneDoc(info.documentation, false);
pushOneRelationship(info.relationships);
}
}
out.push('\n');
};
doc.occurrences.sort(occurrencesByLine);
let occurrenceIndex = 0;
for (const [lineNumber, line] of input.lines.entries()) {
// Write 0,0 items ABOVE the first line.
// This is the only case where we would need to do this.
if (occurrenceIndex === 0) {
const occurrence = doc.occurrences[occurrenceIndex];
const range = Range_1.Range.fromLsif(occurrence.range);
// This is essentially a "file-based" item.
// This guarantees that this sits above everything else in the file.
if (range.start.character === 0 && range.end.character === 0) {
const isDefinition = (occurrence.symbol_roles & scip_1.scip.SymbolRole.Definition) > 0;
out.push(commentSyntax);
out.push(' < ');
out.push(isDefinition ? 'definition' : 'reference');
out.push(' ');
out.push(symbolNameForSnapshot(occurrence.symbol));
pushDoc(range, occurrence.symbol, isDefinition, true);
out.push('\n');
occurrenceIndex++;
}
}
out.push('');
out.push(line);
out.push('\n');
while (occurrenceIndex < doc.occurrences.length &&
doc.occurrences[occurrenceIndex].range[0] === lineNumber) {
const occurrence = doc.occurrences[occurrenceIndex];
occurrenceIndex++;
if (occurrence.symbol === undefined) {
continue;
}
if (occurrence.range.length > 3) {
throw new Error('not yet implemented, multi-line ranges');
}
const range = Range_1.Range.fromLsif(occurrence.range);
out.push(commentSyntax);
const isStartOfLine = range.start.character === 0;
if (!isStartOfLine) {
out.push(' '.repeat(range.start.character - 2));
}
let modifier = 0;
if (isStartOfLine) {
modifier = 1;
}
const caretLength = range.end.character - range.start.character - modifier;
if (caretLength < 0) {
throw new Error(input.format(range, 'negative length occurrence!'));
}
out.push('^'.repeat(caretLength));
out.push(' ');
const isDefinition = (occurrence.symbol_roles & scip_1.scip.SymbolRole.Definition) > 0;
out.push(isDefinition ? 'definition' : 'reference');
out.push(' ');
const symbol = symbolNameForSnapshot(occurrence.symbol);
out.push(symbol.replace('\n', '|'));
pushDoc(range, occurrence.symbol, isDefinition, isStartOfLine);
}
}
return out.join('');
}
exports.formatSnapshot = formatSnapshot;
function occurrencesByLine(a, b) {
return Range_1.Range.fromLsif(a.range).compare(Range_1.Range.fromLsif(b.range));
}