123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
import parseRule from 'Engine/parseRule'
|
|
import { safeLoad } from 'js-yaml'
|
|
import { lensPath, set } from 'ramda'
|
|
import { compilationError } from './error'
|
|
import { parseReference } from './parseReference'
|
|
import { ParsedRules, Rules } from './types'
|
|
|
|
export default function parseRules<Names extends string>(
|
|
rawRules: Rules<Names> | string
|
|
): ParsedRules<Names> {
|
|
const rules =
|
|
typeof rawRules === 'string'
|
|
? (safeLoad(rawRules.replace(/\t/g, ' ')) as Rules<Names>)
|
|
: { ...rawRules }
|
|
|
|
extractInlinedNames(rules)
|
|
|
|
/* First we parse each rule one by one. When a mechanism is encountered, it is
|
|
recursively parsed. When a reference to a variable is encountered, a
|
|
'variable' node is created, we don't parse variables recursively. */
|
|
let parsedRules = {}
|
|
|
|
/* A rule `A` can disable a rule `B` using the rule `rend non applicable: B`
|
|
in the definition of `A`. We need to map these exonerations to be able to
|
|
retreive them from `B` */
|
|
let nonApplicableMapping: Record<string, any> = {}
|
|
let replacedByMapping: Record<string, any> = {}
|
|
;(Object.keys(rules) as Names[]).map(dottedName => {
|
|
const parsedRule = parseRule(rules, dottedName, parsedRules)
|
|
|
|
if (parsedRule['rend non applicable']) {
|
|
nonApplicableMapping[parsedRule.dottedName] =
|
|
parsedRule['rend non applicable']
|
|
}
|
|
|
|
const replaceDescriptors = parsedRule['remplace']
|
|
if (replaceDescriptors) {
|
|
replaceDescriptors.forEach(
|
|
descriptor =>
|
|
(replacedByMapping[descriptor.referenceName] = [
|
|
...(replacedByMapping[descriptor.referenceName] ?? []),
|
|
{ ...descriptor, referenceName: parsedRule.dottedName }
|
|
])
|
|
)
|
|
}
|
|
})
|
|
|
|
Object.entries(nonApplicableMapping).forEach(([a, b]) => {
|
|
b.forEach(ruleName => {
|
|
parsedRules[ruleName].isDisabledBy.push(
|
|
parseReference(rules, parsedRules[ruleName], parsedRules)(a)
|
|
)
|
|
})
|
|
})
|
|
Object.entries(replacedByMapping).forEach(([a, b]) => {
|
|
parsedRules[a].replacedBy = b.map(({ referenceName, ...other }) => ({
|
|
referenceNode: parseReference(
|
|
rules,
|
|
parsedRules[referenceName],
|
|
parsedRules
|
|
)(referenceName),
|
|
...other
|
|
}))
|
|
})
|
|
|
|
return parsedRules as ParsedRules<Names>
|
|
}
|
|
|
|
// We recursively traverse the YAML tree in order to extract named parameters
|
|
// into their own dedicated rules, and replace the inline definition with a
|
|
// reference to the newly created rule.
|
|
function extractInlinedNames(rules: Record<string, Object>) {
|
|
const extractNamesInRule = (dottedName: string) => {
|
|
rules[dottedName] !== null &&
|
|
Object.entries(rules[dottedName]).forEach(
|
|
extractNamesInObject(dottedName)
|
|
)
|
|
}
|
|
const extractNamesInObject = (
|
|
dottedName: string,
|
|
context: Array<string | number> = []
|
|
) => ([key, value]: [string, Object]) => {
|
|
const match = key.match(/\[ref( (.+))?\]$/)
|
|
if (match) {
|
|
const argumentType = key.replace(match[0], '').trim()
|
|
const argumentName = match[2]?.trim() || argumentType
|
|
const extractedReferenceName = `${dottedName} . ${argumentName}`
|
|
|
|
if (typeof rules[extractedReferenceName] !== 'undefined') {
|
|
compilationError(
|
|
dottedName,
|
|
`Le paramètre [ref] ${argumentName} entre en conflit avec la règle déjà existante ${extractedReferenceName}`
|
|
)
|
|
}
|
|
|
|
rules[extractedReferenceName] = {
|
|
formule: value,
|
|
// TODO: The `virtualRule` parameter should be used to avoid creating a
|
|
// dedicated documentation page.
|
|
virtualRule: true
|
|
}
|
|
rules[dottedName] = set(
|
|
lensPath([...context, argumentType]),
|
|
extractedReferenceName,
|
|
rules[dottedName]
|
|
)
|
|
extractNamesInRule(extractedReferenceName)
|
|
} else if (Array.isArray(value)) {
|
|
value.forEach((content: Object, i) =>
|
|
Object.entries(content).forEach(
|
|
extractNamesInObject(dottedName, [...context, key, i])
|
|
)
|
|
)
|
|
} else if (value && typeof value === 'object') {
|
|
Object.entries(value).forEach(
|
|
extractNamesInObject(dottedName, [...context, key])
|
|
)
|
|
}
|
|
}
|
|
|
|
Object.keys(rules).forEach(extractNamesInRule)
|
|
}
|