@frontboi/prettier-plugin-ramses-style
Version:
Pretty your javascript and typescript project to Ramses style.
612 lines (611 loc) • 28.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.preprocessor = preprocessor;
const types_1 = require("@babel/types");
const generate = require('@babel/generator').default;
const babelParser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
/**
* Fonction permettant de trier des éléments du plus court au plus long.
* @param a le premier champ à comparer
* @param b le second champ à comparer
* @returns l'ordre de tri
*/
function sortByLength(a, b) {
return computePropertyLength(a) - computePropertyLength(b);
}
/**
* Permet de calculer le nombre de lignes d'un nœud.
* @param node le noeud dont on veut calculer le nombre de lignes
* @returns le nombre de lignes
*/
function getNodeNbLines(node) {
return node.loc.end.line - node.loc.start.line;
}
/**
* Permet de calculer la taille d'une propriété d'un objet selon son type: en règle générale on prendra la taille du nom et de la valeur associés, mais parfois
* on ne prendra que la taille du nom (si la valeur est une référence à une variable par exemple).
* @param property la propriété dont on veut calculer la taille
* @returns la taille de la propriété
* @throws si le type de propriété n'est pas géré
*/
function computePropertyLength(property) {
var _a, _b, _c, _d;
function getLength(start, end) {
return Math.abs((end || 0) - (start || 0));
}
switch (property.type) {
case 'JSXAttribute':
return getLength(property.name.start, property.name.end) + getLength((_a = property.value) === null || _a === void 0 ? void 0 : _a.start, (_b = property.value) === null || _b === void 0 ? void 0 : _b.end);
case 'Identifier':
case 'StringLiteral':
case 'NumericLiteral':
return getLength(property.start, property.end);
case 'ArrayExpression':
return getLength(property.start, property.end);
case 'ObjectProperty':
// les fonctions sont reclassées en dernières, et elles sont triées entre elles selon le nombre de lignes qu'elles occupent
if (property.value.type === 'ArrowFunctionExpression') {
const nbLines = getLength((_c = property.loc) === null || _c === void 0 ? void 0 : _c.start.line, (_d = property.loc) === null || _d === void 0 ? void 0 : _d.end.line);
if (nbLines === 0) {
return (1000 + getLength(property.key.start, property.key.end) + getLength(property.value.start, property.value.end));
}
else if (nbLines <= 2) {
// @ts-ignore
const lineLength = getLength(property.value.body.body[0].start, property.value.body.body[0].end);
return 1500 + lineLength;
}
else {
return 2000 + nbLines;
}
}
else {
return getLength(property.start, property.end);
}
case 'ObjectMethod':
return 0;
default:
return getLength(property.start, property.end);
}
}
function shiftDescendantLines(node, delta) {
var _a, _b, _c, _d, _e;
if (!node || delta === 0)
return;
const children = node.properties || node.elements || node.members || node.body;
if (!Array.isArray(children))
return;
for (const child of children) {
if (!child)
continue;
if (child.loc) {
child.loc.start.line += delta;
child.loc.end.line += delta;
}
if ((_a = child.key) === null || _a === void 0 ? void 0 : _a.loc) {
child.key.loc.start.line += delta;
child.key.loc.end.line += delta;
}
if ((_b = child.value) === null || _b === void 0 ? void 0 : _b.loc) {
child.value.loc.start.line += delta;
child.value.loc.end.line += delta;
shiftDescendantLines(child.value, delta);
}
if ((_c = child.typeAnnotation) === null || _c === void 0 ? void 0 : _c.loc) {
child.typeAnnotation.loc.start.line += delta;
child.typeAnnotation.loc.end.line += delta;
}
if ((_d = child.id) === null || _d === void 0 ? void 0 : _d.loc) {
child.id.loc.start.line += delta;
child.id.loc.end.line += delta;
}
if ((_e = child.initializer) === null || _e === void 0 ? void 0 : _e.loc) {
child.initializer.loc.start.line += delta;
child.initializer.loc.end.line += delta;
shiftDescendantLines(child.initializer, delta);
}
}
}
/**
* Permet de mettre à jour la loc d'un nœud et de ses enfants.
* @param node le nœud dont on veut mettre à jour la loc
* @param startLine nouvelle ligne de début
* @param endLine nouvelle ligne de fin
*/
function updateNodeLoc(node, startLine, endLine) {
var _a, _b, _c, _d, _e;
const delta = startLine - node.loc.start.line;
node.loc.start.line = startLine;
node.loc.end.line = endLine;
if ((_a = node.key) === null || _a === void 0 ? void 0 : _a.loc) {
node.key.loc.start.line = startLine;
node.key.loc.end.line = endLine;
}
if ((_b = node.value) === null || _b === void 0 ? void 0 : _b.loc) {
node.value.loc.start.line = startLine;
node.value.loc.end.line = endLine;
shiftDescendantLines(node.value, delta);
}
if ((_c = node.typeAnnotation) === null || _c === void 0 ? void 0 : _c.loc) {
node.typeAnnotation.loc.start.line += delta;
node.typeAnnotation.loc.end.line += delta;
}
if ((_d = node.id) === null || _d === void 0 ? void 0 : _d.loc) {
node.id.loc.start.line = startLine;
node.id.loc.end.line = endLine;
}
if ((_e = node.initializer) === null || _e === void 0 ? void 0 : _e.loc) {
node.initializer.loc.start.line = startLine;
node.initializer.loc.end.line = endLine;
shiftDescendantLines(node.initializer, delta);
}
}
/**
* Il se trouve que Babel parse le javascript en partant du principe qu'un commentaire sur la même ligne qu'une ligne de code appartient en fait à la ligne de code suivante en tant que
* trailingComment.
* Cette fonction a pour but de corriger ce problème d'interprétation en déplaçant les commentaires de fin de ligne dans les commentaires de fin de la ligne de code précédente.
* @param nodes les noeuds à traiter
*/
function correctEndLineComments(nodes) {
var _a;
for (let i = 1; i < nodes.length; i++) {
const currentNode = nodes[i];
const previousNode = nodes[i - 1];
const hasABeforeCommentOnPreviousCodeLine = ((_a = currentNode.leadingComments) === null || _a === void 0 ? void 0 : _a[0].loc.start.line) === previousNode.loc.start.line;
if (hasABeforeCommentOnPreviousCodeLine) {
const comment = Object.assign(Object.assign({}, currentNode.leadingComments[0]), { isOnSameLine: true });
comment.loc.start.line = previousNode.loc.end.line;
comment.loc.end.line = previousNode.loc.end.line;
// réassignation du commentaire au noeud précédent
if (!previousNode.trailingComments) {
previousNode.trailingComments = [];
}
previousNode.trailingComments.push(comment);
if (currentNode.leadingComments.length === 1) {
delete currentNode.leadingComments;
}
else {
currentNode.leadingComments.shift();
}
syncCommentsLocation(previousNode);
syncCommentsLocation(currentNode);
}
}
}
function isSameComment(commentA, commentB) {
var _a, _b, _c, _d;
if (!commentA || !commentB)
return false;
if (commentA === commentB)
return true;
return (commentA.type === commentB.type &&
commentA.value === commentB.value &&
((_a = commentA.loc) === null || _a === void 0 ? void 0 : _a.start.line) === ((_b = commentB.loc) === null || _b === void 0 ? void 0 : _b.start.line) &&
((_c = commentA.loc) === null || _c === void 0 ? void 0 : _c.end.line) === ((_d = commentB.loc) === null || _d === void 0 ? void 0 : _d.end.line));
}
/**
* Babel peut dupliquer un même commentaire: trailing sur le nœud précédent et leading sur le suivant.
* Ce doublon provoque un décalage de lignes appliqué deux fois ensuite.
* On retire la version trailing quand elle est identique à une leading du nœud suivant.
* @param nodes les noeuds à normaliser
*/
function removeDuplicatedBoundaryComments(nodes) {
var _a, _b;
for (let i = 1; i < nodes.length; i++) {
const previousNode = nodes[i - 1];
const currentNode = nodes[i];
if (!((_a = previousNode.trailingComments) === null || _a === void 0 ? void 0 : _a.length) || !((_b = currentNode.leadingComments) === null || _b === void 0 ? void 0 : _b.length))
continue;
previousNode.trailingComments = previousNode.trailingComments.filter((trailingComment) => !currentNode.leadingComments.some((leadingComment) => isSameComment(trailingComment, leadingComment)));
if (previousNode.trailingComments.length === 0) {
delete previousNode.trailingComments;
}
}
}
/**
* Synchronise les numéros de ligne des commentaires d'un nœud par rapport à la ligne courante de ce nœud.
* @param node le nœud dont on souhaite mettre à jour les commentaires
*/
function syncCommentsLocation(node) {
var _a, _b;
const nbCommentsBefore = (_a = node.leadingComments) === null || _a === void 0 ? void 0 : _a.length;
const nbCommentsAfter = (_b = node.trailingComments) === null || _b === void 0 ? void 0 : _b.length;
if (nbCommentsBefore > 0) {
// leading comments terminent à la ligne précédente
let startIndex = node.loc.start.line - nbCommentsBefore;
for (let j = 0; j < nbCommentsBefore; j++) {
const comment = node.leadingComments[j];
const commentNbLines = getNodeNbLines(comment);
comment.loc.start.line = startIndex + j;
comment.loc.end.line = startIndex + j + commentNbLines;
}
}
if (nbCommentsAfter > 0) {
// trailings commments commencent à la ligne courante
const startIndex = node.trailingComments[0].isOnSameLine ? 0 : 1;
for (let j = 0; j < nbCommentsAfter; j++) {
const comment = node.trailingComments[j];
const commentNbLines = getNodeNbLines(comment);
comment.loc.start.line = node.loc.end.line + j + startIndex;
comment.loc.end.line = node.loc.end.line + j + startIndex + commentNbLines;
}
}
}
/**
* Cette fonction permet de mettre à jour les numéros de ligne d'une liste de nœuds après un tri: en effet, pour que Prettier réagisse correctement il que l'AST soit fidèle à la réalité.
* Alors il devient nécessaire de mettre à jour les numéros de ligne des nœuds après un tri, et pas seulement celui du nœud lui-même mais aussi de ses enfants.
* @param nodes la liste des nœuds à mettre à jour
* @param initialNode nœud de référence pour la première position à prendre en compte
*/
function updateLoc(nodes, initialNode) {
var _a;
if (nodes.length === 0)
return;
const allOnSameLine = nodes.every(n => n.loc.start.line === nodes[0].loc.start.line) &&
nodes.every(n => getNodeNbLines(n) === 0);
if (allOnSameLine) {
const line = (initialNode === null || initialNode === void 0 ? void 0 : initialNode.loc.start.line) || nodes[0].loc.start.line;
for (const node of nodes) {
updateNodeLoc(node, line, line);
}
return;
}
const initialNodeRealLine = ((initialNode === null || initialNode === void 0 ? void 0 : initialNode.loc.start.line) || 0) - (((_a = initialNode === null || initialNode === void 0 ? void 0 : initialNode.leadingComments) === null || _a === void 0 ? void 0 : _a.length) || 0);
let newStartLine = initialNodeRealLine;
let previousNodeNbLines = -1;
for (const node of nodes) {
const nodeNbLines = getNodeNbLines(node);
const hasCommentOnSameLine = nodeNbLines === 0 && node.trailingComments && node.trailingComments[0].loc.start.line === node.loc.start.line;
newStartLine += previousNodeNbLines + 1;
previousNodeNbLines = nodeNbLines;
const newEndLine = newStartLine + nodeNbLines;
updateNodeLoc(node, newStartLine, newEndLine);
if (hasCommentOnSameLine) {
node.trailingComments[0].loc.start.line = newEndLine;
node.trailingComments[0].loc.end.line = newEndLine;
}
syncCommentsLocation(node);
}
}
/**
* Cette fonction permet de mettre à jour les numéros de ligne de chaque nœud avec commentaire: il faut éventuellement déplacer les nœud qui précèdent ou qui suivent un commentaire.
* @param nodes tous les nœuds à mettre à jour
*/
function updateLocWithComments(nodes) {
var _a, _b;
for (let i = 0; i < nodes.length; i++) {
const nbCommentsBefore = (_a = nodes[i].leadingComments) === null || _a === void 0 ? void 0 : _a.length;
const nbCommentsAfter = (_b = nodes[i].trailingComments) === null || _b === void 0 ? void 0 : _b.length;
if (nbCommentsBefore > 0) {
for (let j = i; j < nodes.length; j++) {
const currentNode = nodes[j];
const newFirstLine = currentNode.loc.start.line + nbCommentsBefore;
const newLastLine = currentNode.loc.end.line + nbCommentsBefore;
updateNodeLoc(currentNode, newFirstLine, newLastLine);
syncCommentsLocation(currentNode);
}
}
if (nbCommentsAfter > 0) {
for (let j = i + 1; j < nodes.length; j++) {
const currentNode = nodes[j];
const newFirstLine = currentNode.loc.start.line + nbCommentsAfter;
const newLastLine = currentNode.loc.end.line + nbCommentsAfter;
updateNodeLoc(currentNode, newFirstLine, newLastLine);
syncCommentsLocation(currentNode);
}
}
}
}
const LEADING_COMMENT_MARKER_PREFIX = '__RAMSES_LEADING_COMMENT__';
function tagLeadingComments(nodes) {
var _a;
for (let i = 0; i < nodes.length; i++) {
const currentNode = nodes[i];
if (!((_a = currentNode.leadingComments) === null || _a === void 0 ? void 0 : _a.length))
continue;
const previousNode = nodes[i - 1];
const hasBlankLineBefore = !!previousNode && currentNode.leadingComments[0].loc.start.line - previousNode.loc.end.line > 1;
const marker = `${LEADING_COMMENT_MARKER_PREFIX}${hasBlankLineBefore ? '2' : '1'}__`;
for (const comment of currentNode.leadingComments) {
if (comment.type !== 'CommentLine')
continue;
if (comment.value.includes(LEADING_COMMENT_MARKER_PREFIX))
continue;
comment.value = `${marker}${comment.value}`;
}
}
}
function restoreLeadingCommentsInCode(code) {
const inlineTaggedCommentPattern = /([,}\]])\s*\/\/\s*__RAMSES_LEADING_COMMENT__([12])__([^\n]*)\n([ \t]*)([^\n]+)/g;
let nextCode = code.replace(inlineTaggedCommentPattern, (_, separator, level, commentText, indent, nextLine) => {
const blankLine = level === '2' ? '\n' : '';
return `${separator}\n${blankLine}${indent}//${commentText}\n${indent}${nextLine}`;
});
nextCode = nextCode.replace(/([^\n])\n([ \t]*)\/\/\s*__RAMSES_LEADING_COMMENT__2__/g, '$1\n\n$2// __RAMSES_LEADING_COMMENT__2__');
nextCode = nextCode.replace(/\/\/\s*__RAMSES_LEADING_COMMENT__[12]__/g, '//');
return nextCode;
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Surrogate pair regex: matches a single non-BMP character (high surrogate + low surrogate)
const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
// Private Use Area (BMP) characters are safer placeholders than control chars.
// They are still 1 code unit each, so a 2-char placeholder preserves surrogate-pair length.
const PRIVATE_USE_START = 0xe000;
const PRIVATE_USE_END = 0xf8ff;
const PRIVATE_USE_RANGE_SIZE = PRIVATE_USE_END - PRIVATE_USE_START + 1;
function buildPrivateUsePlaceholder(index) {
const firstOffset = index % PRIVATE_USE_RANGE_SIZE;
const secondOffset = Math.floor(index / PRIVATE_USE_RANGE_SIZE);
if (secondOffset >= PRIVATE_USE_RANGE_SIZE) {
throw new Error('Too many unique non-BMP characters to protect');
}
const first = String.fromCharCode(PRIVATE_USE_START + firstOffset);
const second = String.fromCharCode(PRIVATE_USE_START + secondOffset);
return first + second;
}
function toUnicodeEscape(value) {
return [...value].map(c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')).join('');
}
/**
* Replaces each unique non-BMP character in the source text with a same-length (2 char) placeholder.
* This preserves all positions in the AST since each surrogate pair (2 code units) is replaced with 2 code units.
* Uses private-use BMP chars to avoid control-character corruption in downstream tooling.
*/
function protectNonBMPInSource(code) {
const charToPlaceholder = new Map();
const placeholderToChar = new Map();
let counter = 0;
const result = code.replace(SURROGATE_PAIR_RE, (match) => {
let placeholder = charToPlaceholder.get(match);
if (!placeholder) {
placeholder = buildPrivateUsePlaceholder(counter);
while (code.includes(placeholder) || placeholderToChar.has(placeholder)) {
counter++;
placeholder = buildPrivateUsePlaceholder(counter);
}
charToPlaceholder.set(match, placeholder);
placeholderToChar.set(placeholder, match);
counter++;
}
return placeholder;
});
return { code: result, map: placeholderToChar };
}
function restoreNonBMPInOutput(code, map) {
let result = code;
for (const [placeholder, original] of map) {
// Handle raw placeholders (when extra.raw is preserved)
result = result.split(placeholder).join(original);
// Handle escaped unicode forms (when extra.raw was lost).
const unicodeEscaped = toUnicodeEscape(placeholder);
result = result.replace(new RegExp(escapeRegExp(unicodeEscaped), 'gi'), original);
}
return result;
}
function fixEscapedSurrogatePairs(code) {
return code.replace(/\\u([dD][89abAB][0-9a-fA-F]{2})\\u([dD][c-fC-F][0-9a-fA-F]{2})/g, (_, high, low) => {
const highCode = parseInt(high, 16);
const lowCode = parseInt(low, 16);
return String.fromCharCode(highCode, lowCode);
});
}
function hasUnpairedSurrogates(text) {
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
const isHigh = code >= 0xd800 && code <= 0xdbff;
const isLow = code >= 0xdc00 && code <= 0xdfff;
if (isHigh) {
const next = text.charCodeAt(i + 1);
const nextIsLow = next >= 0xdc00 && next <= 0xdfff;
if (!nextIsLow)
return true;
i++;
continue;
}
if (isLow)
return true;
}
return false;
}
const DISABLE_MARKER = '@ramses-style-disable';
function hasDisableMarkerInHeaderComment(code) {
let source = code.replace(/^\uFEFF/, '');
// Ignore shebang when present.
if (source.startsWith('#!')) {
const firstLineBreak = source.indexOf('\n');
source = firstLineBreak === -1 ? '' : source.slice(firstLineBreak + 1);
}
const headerCommentMatch = source.match(/^\s*(\/\/[^\n]*|\/\*[\s\S]*?\*\/)/);
if (!headerCommentMatch)
return false;
return headerCommentMatch[1].toLowerCase().includes(DISABLE_MARKER);
}
function collectTypeAliasesWithBlankLine(ast) {
var _a, _b;
const result = [];
const programBody = ((_a = ast === null || ast === void 0 ? void 0 : ast.program) === null || _a === void 0 ? void 0 : _a.body) || [];
for (let i = 1; i < programBody.length; i++) {
const previousNode = programBody[i - 1];
const currentNode = programBody[i];
const declaration = currentNode.type === 'ExportNamedDeclaration' ? currentNode.declaration : currentNode;
if (!declaration || declaration.type !== 'TSTypeAliasDeclaration')
continue;
if (!((_b = declaration.id) === null || _b === void 0 ? void 0 : _b.name))
continue;
if (!(previousNode === null || previousNode === void 0 ? void 0 : previousNode.loc) || !(currentNode === null || currentNode === void 0 ? void 0 : currentNode.loc))
continue;
const hasBlankLineBefore = currentNode.loc.start.line - previousNode.loc.end.line > 1;
if (hasBlankLineBefore) {
result.push(declaration.id.name);
}
}
return result;
}
function restoreBlankLinesBeforeTypeAliases(code, typeAliasNames) {
if (typeAliasNames.length === 0)
return code;
const lines = code.split('\n');
for (const name of typeAliasNames) {
const typePattern = new RegExp(`^\\s*(export\\s+)?type\\s+${escapeRegExp(name)}\\b`);
for (let i = 1; i < lines.length; i++) {
if (!typePattern.test(lines[i]))
continue;
if (lines[i - 1].trim() === '')
break;
lines.splice(i, 0, '');
break;
}
}
return lines.join('\n');
}
/**
* Permet de trier les propriétés d'un arbre AST par ordre de longueur croissante.
* @param unsortedElements les nœuds AST à trier
* @returns les nœuds triés
*/
function colocateLeadingCommentsOfMultilineNodes(nodes) {
var _a;
for (let i = 1; i < nodes.length; i++) {
const currentNode = nodes[i];
const previousNode = nodes[i - 1];
if (!((_a = currentNode.leadingComments) === null || _a === void 0 ? void 0 : _a.length))
continue;
if (getNodeNbLines(currentNode) === 0)
continue;
const firstComment = currentNode.leadingComments[0];
if (firstComment.loc.start.line !== previousNode.loc.end.line)
continue;
const comment = Object.assign(Object.assign({}, firstComment), { isOnSameLine: true });
comment.loc.start.line = previousNode.loc.end.line;
comment.loc.end.line = previousNode.loc.end.line;
if (!previousNode.trailingComments) {
previousNode.trailingComments = [];
}
previousNode.trailingComments.push(comment);
if (currentNode.leadingComments.length === 1) {
delete currentNode.leadingComments;
}
else {
currentNode.leadingComments.shift();
}
syncCommentsLocation(previousNode);
syncCommentsLocation(currentNode);
}
}
function isTypeScriptNode(node) {
return typeof (node === null || node === void 0 ? void 0 : node.type) === 'string' && node.type.startsWith('TS');
}
function sortProperties(unsortedElements) {
const isTypeScriptMembersList = unsortedElements.every(isTypeScriptNode);
const isInlineTypeScriptMembersList = isTypeScriptMembersList &&
unsortedElements.length > 0 &&
unsortedElements.every(node => { var _a, _b, _c, _d, _e; return ((_b = (_a = node === null || node === void 0 ? void 0 : node.loc) === null || _a === void 0 ? void 0 : _a.start) === null || _b === void 0 ? void 0 : _b.line) === ((_e = (_d = (_c = unsortedElements[0]) === null || _c === void 0 ? void 0 : _c.loc) === null || _d === void 0 ? void 0 : _d.start) === null || _e === void 0 ? void 0 : _e.line) && getNodeNbLines(node) === 0; });
const shouldNormalizeCommentsAndLoc = !isInlineTypeScriptMembersList;
// Les nœuds TypeScript inline (ex: Promise<{ ... }>) sont sensibles aux manipulations de loc.
// On évite cette phase pour ne pas perdre des annotations de types lors du print.
if (shouldNormalizeCommentsAndLoc) {
correctEndLineComments(unsortedElements);
removeDuplicatedBoundaryComments(unsortedElements);
tagLeadingComments(unsortedElements);
}
// sauvegarde de l'index des éléments non-triables pour les remettre à la même place ensuite
const fixedPositionNodes = {};
for (let i = 0; i < unsortedElements.length; i++) {
const currentObject = unsortedElements[i];
if ((0, types_1.isSpreadElement)(currentObject) ||
currentObject.type === 'RestElement' ||
currentObject.type === 'JSXSpreadAttribute') {
fixedPositionNodes[i] = currentObject;
}
}
// tri par longueur croissante
const sortedElements = unsortedElements
.filter(e => !Object.values(fixedPositionNodes).includes(e) && e.type !== 'ObjectMethod')
.slice()
.sort(sortByLength);
if (shouldNormalizeCommentsAndLoc) {
updateLoc(sortedElements, unsortedElements[0]);
colocateLeadingCommentsOfMultilineNodes(sortedElements);
updateLocWithComments(sortedElements);
}
// réinsertion des éléments non-triables à leur place
for (const [index, node] of Object.entries(fixedPositionNodes)) {
sortedElements.splice(+index, 0, node);
}
// réinjection des object method
for (let i = 0; i < unsortedElements.length; i++) {
if (unsortedElements[i].type === 'ObjectMethod') {
sortedElements.splice(i, 0, unsortedElements[i]);
}
}
return sortedElements;
}
/**
* Fonction permettant de d'améliorer la syntaxe d'un code en triant des champs répartis ligne par ligne par ordre de longueur croissante:
* cela signifie que les champs les plus courts seront placés en premier (le nom et la valeur de la propriété sont tous deux pris en compte).
* Les champs concernés sont les suivants:
* - les champs d'un objet (destructuration, retour de fonction)
* - les paramètres d'une fonction
* - les éléments d'un tableau
* @param code le code à traiter
* @param options options passées à Prettier
* @returns le code modifié
*/
function preprocessor(code, options) {
if (hasDisableMarkerInHeaderComment(code)) {
return code;
}
const { code: safeCode, map: nonBMPMap } = protectNonBMPInSource(code);
const ast = babelParser.parse(safeCode, {
plugins: ['jsx', 'typescript'],
sourceType: 'module',
});
const typeAliasesWithBlankLine = collectTypeAliasesWithBlankLine(ast);
traverse(ast, {
// éléments d'un objet (ex: déclaration de variable, retour de fonction)
ObjectExpression(path) {
const sortedElements = sortProperties(path.node.properties);
path.node.properties = sortedElements;
},
// destructuration d'objet
ObjectPattern(path) {
const sortedElements = sortProperties(path.node.properties);
path.node.properties = sortedElements;
},
// TypeScript interfaces
TSInterfaceBody(path) {
const sortedElements = sortProperties(path.node.body);
path.node.body = sortedElements;
},
// TypeScript type literals (e.g. type Foo = { ... })
TSTypeLiteral(path) {
const sortedElements = sortProperties(path.node.members);
path.node.members = sortedElements;
},
// TypeScript enums
TSEnumDeclaration(path) {
const sortedElements = sortProperties(path.node.members);
path.node.members = sortedElements;
},
// JSX props
JSXOpeningElement(path) {
const sortedAttributes = sortProperties(path.node.attributes);
path.node.attributes = sortedAttributes;
},
});
const newCode = generate(ast, {
retainLines: true,
}).code;
const fixedEscapesCode = fixEscapedSurrogatePairs(newCode);
const restoredLeadingCommentsCode = restoreLeadingCommentsInCode(fixedEscapesCode);
const restoredBlankLinesCode = restoreBlankLinesBeforeTypeAliases(restoredLeadingCommentsCode, typeAliasesWithBlankLine);
const cleanedCode = restoredBlankLinesCode.replace(/\n{3,}/g, '\n\n');
const restoredCode = restoreNonBMPInOutput(cleanedCode, nonBMPMap);
// Safety net: never emit potentially corrupted unicode output.
if ((restoredCode.includes('�') && !code.includes('�')) || hasUnpairedSurrogates(restoredCode)) {
return code;
}
return restoredCode;
}