blob: 6672712d5a8461b55dba5d1a0ff0ec90a8137a53 [file] [log] [blame]
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {SubmitRequirementExpressionInfo} from '../api/rest-api';
import {Execution} from '../constants/reporting';
import {getAppContext} from '../services/app-context';
export enum SubmitRequirementExpressionAtomStatus {
UNKNOWN = 'UNKNOWN',
PASSING = 'PASSING',
FAILING = 'FAILING',
}
export interface SubmitRequirementExpressionPart {
value: string;
isAtom: boolean;
// Defined iff isAtom is true.
atomStatus?: SubmitRequirementExpressionAtomStatus;
}
interface AtomMatch {
start: number;
end: number;
isPassing: boolean;
}
function appendAllOccurrences(
text: string,
match: string,
isPassing: boolean,
matchedAtoms: AtomMatch[]
) {
for (let searchStartIndex = 0; ; ) {
let index = text.indexOf(match, searchStartIndex);
if (index === -1) {
break;
}
searchStartIndex = index + match.length;
// Include unary minus.
if (index !== 0 && text[index - 1] === '-') {
--index;
isPassing = !isPassing;
}
matchedAtoms.push({start: index, end: searchStartIndex, isPassing});
}
}
function splitExpressionIntoParts(
expression: string,
matchedAtoms: AtomMatch[]
): SubmitRequirementExpressionPart[] {
const result: SubmitRequirementExpressionPart[] = [];
let currentIndex = 0;
for (const {start, end, isPassing} of matchedAtoms) {
if (start < currentIndex) {
getAppContext().reportingService.reportExecution(
Execution.REACHABLE_CODE,
'Overlapping atom matches in submit requirement expression.'
);
continue;
}
if (start > currentIndex) {
result.push({
value: expression.slice(currentIndex, start),
isAtom: false,
});
}
result.push({
value: expression.slice(start, end),
isAtom: true,
atomStatus: isPassing
? SubmitRequirementExpressionAtomStatus.PASSING
: SubmitRequirementExpressionAtomStatus.FAILING,
});
currentIndex = end;
}
if (currentIndex < expression.length) {
result.push({
value: expression.slice(currentIndex),
isAtom: false,
});
}
return result;
}
/**
* Returns expression string split into ExpressionPart.
*
* Concatenation result of all parts is equal to original expression string.
*
* Unary minus is included in the atom and is accounted in the status.
*/
export function atomizeExpression(
expression: SubmitRequirementExpressionInfo
): SubmitRequirementExpressionPart[] {
const matchedAtoms: AtomMatch[] = [];
expression.passing_atoms?.forEach(atom =>
appendAllOccurrences(
expression.expression,
atom,
/* isPassing=*/ true,
matchedAtoms
)
);
expression.failing_atoms?.forEach(atom =>
appendAllOccurrences(
expression.expression,
atom,
/* isPassing=*/ false,
matchedAtoms
)
);
matchedAtoms.sort((a, b) => a.start - b.start);
return splitExpressionIntoParts(expression.expression, matchedAtoms);
}