Skip to content
Draft
31 changes: 28 additions & 3 deletions extension/server/src/providers/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,22 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara
scope.constants
.filter(constant => constant.position && constant.position.path === currentPath)
.forEach(def => {
const isEnum = def.subItems && def.subItems.length > 0;
const constantDef = DocumentSymbol.create(
def.name,
prettyKeywords(def.keyword),
SymbolKind.Constant,
isEnum ? SymbolKind.Enum : SymbolKind.Constant,
Range.create(def.range.start!, 0, def.range.end!, 0),
Range.create(def.range.start!, 0, def.range.end!, 0)
);

if (def.subItems.length > 0) {
if (isEnum) {
constantDef.children = def.subItems
.filter(subitem => subitem.position && subitem.position.path === currentPath)
.map(subitem => DocumentSymbol.create(
subitem.name,
prettyKeywords(subitem.keyword),
SymbolKind.Property,
SymbolKind.EnumMember,
Range.create(subitem.range.start!, 0, subitem.range.start!, 0),
Range.create(subitem.range.end!, 0, subitem.range.end!, 0)
));
Expand Down Expand Up @@ -170,6 +171,30 @@ export default async function documentSymbolProvider(handler: DocumentSymbolPara
currentScopeDefs.push(expandStruct(struct));
});

scope.inputs
.filter(input => input.position && input.position.path === currentPath && validRange(input))
.forEach(input => {
const inputSymbol = DocumentSymbol.create(
input.name,
prettyKeywords(input.keyword),
SymbolKind.Interface,
Range.create(input.range.start!, 0, input.range.end!, 0),
Range.create(input.range.start!, 0, input.range.end!, 0)
);

inputSymbol.children = input.subItems
.filter(subitem => subitem.position && subitem.position.path === currentPath && validRange(subitem))
.map(subitem => DocumentSymbol.create(
subitem.name,
prettyKeywords(subitem.keyword),
SymbolKind.Property,
Range.create(subitem.range.start!, 0, subitem.range.end!, 0),
Range.create(subitem.range.start!, 0, subitem.range.end!, 0)
));

currentScopeDefs.push(inputSymbol);
});

return currentScopeDefs;
};

Expand Down
83 changes: 69 additions & 14 deletions language/models/cache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CacheProps, IncludeStatement, Keywords } from "../parserTypes";
import { trimQuotes } from "../tokens";
import { IRange } from "../types";
import Declaration, { DeclarationType } from "./declaration";

Expand Down Expand Up @@ -89,6 +90,10 @@ export default class Cache {
return this.symbols.filter(s => s.type === `file`);
}

get inputs() {
return this.symbols.filter(s => s.type === `input`);
}

get constants() {
return this.symbols.filter(s => s.type === `constant`);
}
Expand Down Expand Up @@ -142,7 +147,7 @@ export default class Cache {
return (lines.length >= 1 ? lines[0] : 0);
}

find(name: string, specificType?: DeclarationType): Declaration | undefined {
find(name: string, specificType?: DeclarationType, ignorePrefix?: boolean): Declaration | undefined {
name = name.toUpperCase();

const existing = this.symbolRegister.get(name);
Expand All @@ -162,32 +167,82 @@ export default class Cache {
}
}

// If we didn't find it, let's check for subfields
const [subfield] = this.findSubfields(name, ignorePrefix, true);

return subfield;
}

findAll(name: string, ignorePrefix?: boolean): Declaration[] {
name = name.toUpperCase();
let symbols = this.symbolRegister.get(name) || [];

symbols.push(...this.findSubfields(name, ignorePrefix));

// Remove duplicates by position, since we can have the same reference to symbols in structures due to I-spec
symbols = symbols.filter((s, index, self) => {
return self.findIndex(item => item.position.path === s.position.path && s.position.range.line === item.position.range.line) === index;
});

return symbols || [];
}

private findSubfields(name: string, ignorePrefix: boolean, onlyOne?: boolean): Declaration[] {
let symbols: Declaration[] = [];

// Additional logic to check for subItems in symbols
const symbolsWithSubs = [...this.structs, ...this.files];
const symbolsWithSubs = [...this.structs, ...this.files, ...this.inputs];

const subNameIsValid = (sub: Declaration, name: string, prefix?: string) => {
if (prefix) {
name = `${prefix}${name}`;
}

return sub.name.toUpperCase() === name;
}

// First we do a loop to check all names without changing the prefix.
// This only applied to files
for (const struct of symbolsWithSubs) {
if (struct.keyword[`QUALIFIED`] !== true) {

// If the symbol is qualified, we need to check the subItems
const subItem = struct.subItems.find(sub => sub.name.toUpperCase() === name);
if (subItem) return subItem;
const subItem = struct.subItems.find(sub => subNameIsValid(sub, name));
if (subItem) {
symbols.push(subItem);
if (onlyOne) return symbols;
}

// If it's a file, we also need to check the subItems of the file's recordformats
for (const subFile of struct.subItems) {
const subSubItem = subFile.subItems.find(sub => subNameIsValid(sub, name));
if (subSubItem) {
symbols.push(subSubItem);
if (onlyOne) return symbols;
}
}
}
}

// Then we check the names, ignoring the prefix
if (ignorePrefix) {
for (const struct of symbolsWithSubs) {
if (struct.type === `file` && struct.keyword[`QUALIFIED`] !== true) {
const prefix = ignorePrefix && struct.keyword[`PREFIX`] && typeof struct.keyword[`PREFIX`] === `string` ? trimQuotes(struct.keyword[`PREFIX`].toUpperCase()) : ``;

if (struct.type === `file`) {
// If it's a file, we also need to check the subItems of the file's recordformats
for (const subFile of struct.subItems) {
const subSubItem = subFile.subItems.find(sub => sub.name.toUpperCase() === name);
if (subSubItem) return subSubItem;
const subSubItem = subFile.subItems.find(sub => subNameIsValid(sub, name, prefix));
if (subSubItem) {
symbols.push(subSubItem);
if (onlyOne) return symbols;
}
}
}
}
}

return;
}

findAll(name: string): Declaration[] {
name = name.toUpperCase();
const symbols = this.symbolRegister.get(name);
return symbols || [];
return symbols;
}

public findProcedurebyLine(lineNumber: number): Declaration | undefined {
Expand Down
2 changes: 1 addition & 1 deletion language/models/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Keywords, Reference } from "../parserTypes";
import { IRangeWithLine } from "../types";
import Cache from "./cache";

export type DeclarationType = "parameter"|"procedure"|"subroutine"|"file"|"struct"|"subitem"|"variable"|"constant"|"tag"|"indicator";
export type DeclarationType = "parameter"|"procedure"|"subroutine"|"file"|"struct"|"subitem"|"variable"|"constant"|"tag"|"indicator"|"input";

export default class Declaration {
name: string = ``;
Expand Down
134 changes: 120 additions & 14 deletions language/models/fixed.js → language/models/fixed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

import Parser from "../parser";
import { Keywords } from "../parserTypes";
import { Token } from "../types";

/**
* @param {number} lineNumber
Expand All @@ -7,7 +10,7 @@ import Parser from "../parser";
* @param {string} [type]
* @returns {import("../types").Token|undefined}
*/
function calculateToken(lineNumber, startingPos, value, type) {
function calculateToken(lineNumber: number, startingPos: number, value: string, type?: string): Token | undefined {
let resultValue = value.trim();

if (resultValue === ``) {
Expand Down Expand Up @@ -62,7 +65,7 @@ export function parseFLine(lineNumber, lineIndex, content) {
/**
* @param {string} content
*/
export function parseCLine(lineNumber, lineIndex, content) {
export function parseCLine(lineNumber: number, lineIndex: number, content: string) {
content = content.padEnd(80);
const clIndicator = content.substr(7, 8).toUpperCase();
const indicator = content.substr(9, 11);
Expand Down Expand Up @@ -97,10 +100,7 @@ export function parseCLine(lineNumber, lineIndex, content) {
};
}

/**
* @param {string} content
*/
export function parseDLine(lineNumber, lineIndex, content) {
export function parseDLine(lineNumber: number, lineIndex: number, content: string) {
content = content.padEnd(80);
const longForm = content.substring(6).trimEnd();
const potentialName = longForm.endsWith(`...`) ? calculateToken(lineNumber, lineIndex+6, longForm.substring(0, longForm.length - 3)) : undefined;
Expand Down Expand Up @@ -129,7 +129,7 @@ export function parseDLine(lineNumber, lineIndex, content) {
/**
* @param {string} content
*/
export function parsePLine(content, lineNumber, lineIndex) {
export function parsePLine(content: string, lineNumber: number, lineIndex: number) {
content = content.padEnd(80);
let name = content.substr(6, 16).trimEnd();
if (name.endsWith(`...`)) {
Expand All @@ -151,7 +151,7 @@ export function parsePLine(content, lineNumber, lineIndex) {
};
}

export function prettyTypeFromToken(dSpec) {
export function prettyTypeFromDSpecTokens(dSpec) {
return getPrettyType({
type: dSpec.type ? dSpec.type.value : ``,
keywords: dSpec.keywords,
Expand All @@ -162,12 +162,18 @@ export function prettyTypeFromToken(dSpec) {
})
}

/**
*
* @param {{type: string, keywords: import("../parserTypes").Keywords, len: string, pos: string, decimals: string, field: string}} lineData
* @returns {import("../parserTypes").Keywords}
*/
export function getPrettyType(lineData) {
export function prettyTypeFromISpecTokens(iSpec) {
return getPrettyType({
type: iSpec.dataFormat ? iSpec.dataFormat.value : ``,
keywords: {},
len: iSpec.length ? iSpec.length.value : ``,
pos: iSpec.fieldLocation ? iSpec.fieldLocation.value : ``,
decimals: iSpec.decimalPositions ? iSpec.decimalPositions.value : ``,
field: ``
})
}

export function getPrettyType(lineData: {type: string, keywords: Keywords, len: string, pos: string, decimals: string, field: string}): Keywords {
let outType = ``;
let length = Number(lineData.len);

Expand Down Expand Up @@ -304,4 +310,104 @@ export function getPrettyType(lineData) {
}

return Parser.expandKeywords(Parser.getTokens(outType));
}

// https://www.ibm.com/docs/fr/i/7.4.0?topic=specifications-input
export function parseISpec(lineNumber: number, lineIndex: number, content: string) {
content = content.padEnd(80);
let iType: "programRecord"|"programField"|"externalRecord"|"externalField"|undefined;

const name = content.substring(6, 16).trimEnd();

if (name) {
// RECORD
const externalReserved = content.substring(15, 20).trim();
if (externalReserved) {
// If this reserved area is not empty, then it is a program record
iType = `programRecord`;
} else {
iType = `externalRecord`;
}

} else {
// FIELD
const externalName = content.substring(20, 30).trim();

if (externalName) {
iType = `externalField`;
} else {
iType = `programField`;
}
}

const getPart = (start: number, end: number, type?: string) => {
return calculateToken(lineNumber, lineIndex + (start-1), content.substring(start-1, end).trimEnd(), type);
}

switch (iType) {
case `programRecord`:
// Handle program record
// https://www.ibm.com/docs/fr/i/7.4.0?topic=specifications-record-identification-entries#iri

return {
iType,
name: getPart(7, 16),
logicalRelationship: getPart(16, 18),
sequence: getPart(18, 19),
number: getPart(19, 20),
option: getPart(20, 21),
recordIdentifyingIndicator: getPart(21, 23, `special-ind`) // 2 characters
}
break;
case `programField`:
// Handle program field
// https://www.ibm.com/docs/fr/i/7.4.0?topic=specifications-field-description-entries#ifd

return {
iType,
dataAttributes: getPart(31, 34),
dateTimeSeparator: getPart(35, 36),
dataFormat: getPart(36, 37),
fieldLocation: getPart(37, 41),
length: getPart(42, 46),
decimalPositions: getPart(47, 48),
fieldName: getPart(49, 62),
controlLevel: getPart(63, 64, `special-ind`),
matchingFields: getPart(65, 66, `special-ind`),
fieldRecordRelation: getPart(67, 68, `special-ind`),
fieldIndicators: [
getPart(69, 70, `special-ind`),
getPart(71, 72, `special-ind`),
getPart(73, 74, `special-ind`)
]
}

case `externalRecord`:
// Handle external record
// https://www.ibm.com/docs/fr/i/7.4.0?topic=is-record-identification-entries#ier

return {
iType,
name: getPart(7, 16),
recordIdentifyingIndicator: getPart(21, 22, `special-ind`), // 2 characters
};
break;
case `externalField`:
// Handle external field
// https://www.ibm.com/docs/fr/i/7.4.0?topic=is-field-description-entries#ied

return {
iType,
externalName: getPart(21, 30),
fieldName: getPart(49, 62),
controlLevel: getPart(63, 64, `special-ind`),
matchingFields: getPart(65, 66, `special-ind`),
fieldIndicators: [
getPart(69, 70, `special-ind`),
getPart(71, 72, `special-ind`),
getPart(73, 74, `special-ind`)
]
};
break;
}
}
Loading