prettier-plugin-apex
Advanced tools
Comparing version 2.1.5 to 2.2.0-beta.3
@@ -58,2 +58,5 @@ export declare const APEX_TYPES: { | ||
STRUCTURED_VERSION: "apex.jorje.data.ast.VersionRef$StructuredVersion"; | ||
TRUE_ANNOTATION_VALUE: "apex.jorje.data.ast.AnnotationValue$TrueAnnotationValue"; | ||
FALSE_ANNOTATION_VALUE: "apex.jorje.data.ast.AnnotationValue$FalseAnnotationValue"; | ||
STRING_ANNOTATION_VALUE: "apex.jorje.data.ast.AnnotationValue$StringAnnotationValue"; | ||
BINARY_OPERATOR: "apex.jorje.data.ast.BinaryOp"; | ||
@@ -120,8 +123,16 @@ ASSIGNMENT_OPERATOR: "apex.jorje.data.ast.AssignmentOp"; | ||
FIND_VALUE: "apex.jorje.data.sosl.FindValue"; | ||
FIND_VALUE_STRING: "apex.jorje.data.sosl.FindValue$FindString"; | ||
FIND_VALUE_EXPRESSION: "apex.jorje.data.sosl.FindValue$FindExpr"; | ||
IN_CLAUSE: "apex.jorje.data.sosl.InClause"; | ||
WITH_DIVISION_CLAUSE: "apex.jorje.data.sosl.WithDivisionClause"; | ||
DIVISION_VALUE: "apex.jorje.data.sosl.DivisionValue"; | ||
DIVISION_VALUE_LITERAL: "apex.jorje.data.sosl.DivisionValue$DivisionLiteral"; | ||
DIVISION_VALUE_EXPRESSION: "apex.jorje.data.sosl.DivisionValue$DivisionExpr"; | ||
WITH_DATA_CATEGORY_CLAUSE: "apex.jorje.data.sosl.WithDataCategoryClause"; | ||
SEARCH_WITH_CLAUSE: "apex.jorje.data.sosl.SearchWithClause"; | ||
SEARCH_WITH_CLAUSE_VALUE: "apex.jorje.data.sosl.SearchWithClauseValue"; | ||
SEARCH_WITH_CLAUSE_VALUE_STRING: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithStringValue"; | ||
SEARCH_WITH_CLAUSE_VALUE_TARGET: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithTargetValue"; | ||
SEARCH_WITH_CLAUSE_VALUE_TRUE: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithTrueValue"; | ||
SEARCH_WITH_CLAUSE_VALUE_FALSE: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithFalseValue"; | ||
RETURNING_CLAUSE: "apex.jorje.data.sosl.ReturningClause"; | ||
@@ -157,5 +168,9 @@ RETURNING_EXPRESSION: "apex.jorje.data.sosl.ReturningExpr"; | ||
ORDER_BY_EXPRESSION: "apex.jorje.data.soql.OrderByExpr"; | ||
ORDER_BY_EXPRESSION_DISTANCE: "apex.jorje.data.soql.OrderByExpr$OrderByDistance"; | ||
ORDER_BY_EXPRESSION_VALUE: "apex.jorje.data.soql.OrderByExpr$OrderByValue"; | ||
GROUP_BY_CLAUSE: "apex.jorje.data.soql.GroupByClause"; | ||
GROUP_BY_EXPRESSION: "apex.jorje.data.soql.GroupByExpr"; | ||
GROUP_BY_TYPE: "apex.jorje.data.soql.GroupByType"; | ||
GROUP_BY_TYPE_ROLL_UP: "apex.jorje.data.soql.GroupByType$GroupByRollUp"; | ||
GROUP_BY_TYPE_CUBE: "apex.jorje.data.soql.GroupByType$GroupByCube"; | ||
HAVING_CLAUSE: "apex.jorje.data.soql.HavingClause"; | ||
@@ -187,3 +202,7 @@ LIMIT_VALUE: "apex.jorje.data.soql.LimitClause$LimitValue"; | ||
QUERY_LITERAL_NUMBER: "apex.jorje.data.soql.QueryLiteral$QueryNumber"; | ||
QUERY_LITERAL_DATE: "apex.jorje.data.soql.QueryLiteral$QueryDate"; | ||
QUERY_LITERAL_DATE_TIME: "apex.jorje.data.soql.QueryLiteral$QueryDateTime"; | ||
QUERY_LITERAL_TIME: "apex.jorje.data.soql.QueryLiteral$QueryTime"; | ||
QUERY_LITERAL_DATE_FORMULA: "apex.jorje.data.soql.QueryLiteral$QueryDateFormula"; | ||
QUERY_LITERAL_MULTI_CURRENCY: "apex.jorje.data.soql.QueryLiteral$QueryMultiCurrency"; | ||
QUERY_OPERATOR: "apex.jorje.data.soql.QueryOp"; | ||
@@ -194,7 +213,16 @@ QUERY_OPERATOR_LIKE: "apex.jorje.data.soql.QueryOp$QueryLike"; | ||
TRACKING_TYPE: "apex.jorje.data.soql.TrackingType"; | ||
TRACKING_TYPE_FOR_VIEW: "apex.jorje.data.soql.TrackingType$ForView"; | ||
TRACKING_TYPE_FOR_REFERENCE: "apex.jorje.data.soql.TrackingType$ForReference"; | ||
QUERY_OPTION: "apex.jorje.data.soql.QueryOption"; | ||
QUERY_OPTION_LOCK_ROWS: "apex.jorje.data.soql.QueryOption$LockRows"; | ||
QUERY_OPTION_INCLUDE_DELETED: "apex.jorje.data.soql.QueryOption$IncludeDeleted"; | ||
QUERY_USING_CLAUSE: "apex.jorje.data.soql.QueryUsingClause"; | ||
USING_EXPRESSION: "apex.jorje.data.soql.UsingExpr"; | ||
USING_EXPRESSION_USING: "apex.jorje.data.soql.UsingExpr$Using"; | ||
USING_EXPRESSION_USING_EQUALS: "apex.jorje.data.soql.UsingExpr$UsingEquals"; | ||
USING_EXPRESSION_USING_ID: "apex.jorje.data.soql.UsingExpr$UsingId"; | ||
UPDATE_STATS_CLAUSE: "apex.jorje.data.soql.UpdateStatsClause"; | ||
UPDATE_STATS_OPTION: "apex.jorje.data.soql.UpdateStatsOption"; | ||
UPDATE_STATS_OPTION_TRACKING: "apex.jorje.data.soql.UpdateStatsOption$UpdateTracking"; | ||
UPDATE_STATS_OPTION_VIEW_STAT: "apex.jorje.data.soql.UpdateStatsOption$UpdateViewStat"; | ||
WITH_VALUE: "apex.jorje.data.soql.WithClause$WithValue"; | ||
@@ -201,0 +229,0 @@ WITH_DATA_CATEGORIES: "apex.jorje.data.soql.WithClause$WithDataCategories"; |
@@ -58,2 +58,5 @@ export const APEX_TYPES = { | ||
STRUCTURED_VERSION: "apex.jorje.data.ast.VersionRef$StructuredVersion", | ||
TRUE_ANNOTATION_VALUE: "apex.jorje.data.ast.AnnotationValue$TrueAnnotationValue", | ||
FALSE_ANNOTATION_VALUE: "apex.jorje.data.ast.AnnotationValue$FalseAnnotationValue", | ||
STRING_ANNOTATION_VALUE: "apex.jorje.data.ast.AnnotationValue$StringAnnotationValue", | ||
// Operators | ||
@@ -127,8 +130,16 @@ BINARY_OPERATOR: "apex.jorje.data.ast.BinaryOp", | ||
FIND_VALUE: "apex.jorje.data.sosl.FindValue", | ||
FIND_VALUE_STRING: "apex.jorje.data.sosl.FindValue$FindString", | ||
FIND_VALUE_EXPRESSION: "apex.jorje.data.sosl.FindValue$FindExpr", | ||
IN_CLAUSE: "apex.jorje.data.sosl.InClause", | ||
WITH_DIVISION_CLAUSE: "apex.jorje.data.sosl.WithDivisionClause", | ||
DIVISION_VALUE: "apex.jorje.data.sosl.DivisionValue", | ||
DIVISION_VALUE_LITERAL: "apex.jorje.data.sosl.DivisionValue$DivisionLiteral", | ||
DIVISION_VALUE_EXPRESSION: "apex.jorje.data.sosl.DivisionValue$DivisionExpr", | ||
WITH_DATA_CATEGORY_CLAUSE: "apex.jorje.data.sosl.WithDataCategoryClause", | ||
SEARCH_WITH_CLAUSE: "apex.jorje.data.sosl.SearchWithClause", | ||
SEARCH_WITH_CLAUSE_VALUE: "apex.jorje.data.sosl.SearchWithClauseValue", | ||
SEARCH_WITH_CLAUSE_VALUE_STRING: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithStringValue", | ||
SEARCH_WITH_CLAUSE_VALUE_TARGET: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithTargetValue", | ||
SEARCH_WITH_CLAUSE_VALUE_TRUE: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithTrueValue", | ||
SEARCH_WITH_CLAUSE_VALUE_FALSE: "apex.jorje.data.sosl.SearchWithClauseValue$SearchWithFalseValue", | ||
RETURNING_CLAUSE: "apex.jorje.data.sosl.ReturningClause", | ||
@@ -165,5 +176,9 @@ RETURNING_EXPRESSION: "apex.jorje.data.sosl.ReturningExpr", | ||
ORDER_BY_EXPRESSION: "apex.jorje.data.soql.OrderByExpr", | ||
ORDER_BY_EXPRESSION_DISTANCE: "apex.jorje.data.soql.OrderByExpr$OrderByDistance", | ||
ORDER_BY_EXPRESSION_VALUE: "apex.jorje.data.soql.OrderByExpr$OrderByValue", | ||
GROUP_BY_CLAUSE: "apex.jorje.data.soql.GroupByClause", | ||
GROUP_BY_EXPRESSION: "apex.jorje.data.soql.GroupByExpr", | ||
GROUP_BY_TYPE: "apex.jorje.data.soql.GroupByType", | ||
GROUP_BY_TYPE_ROLL_UP: "apex.jorje.data.soql.GroupByType$GroupByRollUp", | ||
GROUP_BY_TYPE_CUBE: "apex.jorje.data.soql.GroupByType$GroupByCube", | ||
HAVING_CLAUSE: "apex.jorje.data.soql.HavingClause", | ||
@@ -195,3 +210,7 @@ LIMIT_VALUE: "apex.jorje.data.soql.LimitClause$LimitValue", | ||
QUERY_LITERAL_NUMBER: "apex.jorje.data.soql.QueryLiteral$QueryNumber", | ||
QUERY_LITERAL_DATE: "apex.jorje.data.soql.QueryLiteral$QueryDate", | ||
QUERY_LITERAL_DATE_TIME: "apex.jorje.data.soql.QueryLiteral$QueryDateTime", | ||
QUERY_LITERAL_TIME: "apex.jorje.data.soql.QueryLiteral$QueryTime", | ||
QUERY_LITERAL_DATE_FORMULA: "apex.jorje.data.soql.QueryLiteral$QueryDateFormula", | ||
QUERY_LITERAL_MULTI_CURRENCY: "apex.jorje.data.soql.QueryLiteral$QueryMultiCurrency", | ||
QUERY_OPERATOR: "apex.jorje.data.soql.QueryOp", | ||
@@ -202,7 +221,16 @@ QUERY_OPERATOR_LIKE: "apex.jorje.data.soql.QueryOp$QueryLike", | ||
TRACKING_TYPE: "apex.jorje.data.soql.TrackingType", | ||
TRACKING_TYPE_FOR_VIEW: "apex.jorje.data.soql.TrackingType$ForView", | ||
TRACKING_TYPE_FOR_REFERENCE: "apex.jorje.data.soql.TrackingType$ForReference", | ||
QUERY_OPTION: "apex.jorje.data.soql.QueryOption", | ||
QUERY_OPTION_LOCK_ROWS: "apex.jorje.data.soql.QueryOption$LockRows", | ||
QUERY_OPTION_INCLUDE_DELETED: "apex.jorje.data.soql.QueryOption$IncludeDeleted", | ||
QUERY_USING_CLAUSE: "apex.jorje.data.soql.QueryUsingClause", | ||
USING_EXPRESSION: "apex.jorje.data.soql.UsingExpr", | ||
USING_EXPRESSION_USING: "apex.jorje.data.soql.UsingExpr$Using", | ||
USING_EXPRESSION_USING_EQUALS: "apex.jorje.data.soql.UsingExpr$UsingEquals", | ||
USING_EXPRESSION_USING_ID: "apex.jorje.data.soql.UsingExpr$UsingId", | ||
UPDATE_STATS_CLAUSE: "apex.jorje.data.soql.UpdateStatsClause", | ||
UPDATE_STATS_OPTION: "apex.jorje.data.soql.UpdateStatsOption", | ||
UPDATE_STATS_OPTION_TRACKING: "apex.jorje.data.soql.UpdateStatsOption$UpdateTracking", | ||
UPDATE_STATS_OPTION_VIEW_STAT: "apex.jorje.data.soql.UpdateStatsOption$UpdateViewStat", | ||
WITH_VALUE: "apex.jorje.data.soql.WithClause$WithValue", | ||
@@ -209,0 +237,0 @@ WITH_DATA_CATEGORIES: "apex.jorje.data.soql.WithClause$WithDataCategories", |
@@ -70,3 +70,3 @@ import { canAttachComment, handleEndOfLineComment, handleOwnLineComment, handleRemainingComment, hasPrettierIgnore, isBlockComment, printComment, willPrintOwnComments, } from "./comments.js"; | ||
category: CATEGORY_APEX, | ||
default: "none", | ||
default: "native", | ||
choices: [ | ||
@@ -83,3 +83,3 @@ { | ||
value: "native", | ||
description: "Use native executable parser", | ||
description: "Use native executable parser, with fallback to Java binaries", | ||
}, | ||
@@ -86,0 +86,0 @@ ], |
@@ -6,5 +6,5 @@ /* eslint no-param-reassign: 0 */ | ||
import { ALLOW_TRAILING_EMPTY_LINE, APEX_TYPES, TRAILING_EMPTY_LINE_AFTER_LAST_NODE, } from "./constants.js"; | ||
import { doesFileExist, findNextUncommentedCharacter, getNativeExecutable, getSerializerBinDirectory, } from "./util.js"; | ||
import { findNextUncommentedCharacter, getNativeExecutableWithFallback, getParentType, getSerializerBinDirectory, } from "./util.js"; | ||
async function parseTextWithSpawn(executable, text, anonymous) { | ||
const args = ["-f", "json", "-i"]; | ||
const args = []; | ||
if (anonymous) { | ||
@@ -57,4 +57,2 @@ args.push("-a"); | ||
anonymous, | ||
outputFormat: "json", | ||
idRef: true, | ||
prettyPrint: false, | ||
@@ -80,22 +78,2 @@ }), | ||
} | ||
// The serialized string given back contains references (to avoid circular references), | ||
// which need to be resolved. This method recursively walks through the | ||
// deserialized object and resolve those references. | ||
function resolveAstReferences(node, referenceMap) { | ||
const nodeId = node["@id"]; | ||
const nodeReference = node["@reference"]; | ||
if (nodeId) { | ||
referenceMap[nodeId] = node; | ||
} | ||
if (nodeReference) { | ||
// If it has a reference attribute, that means it's a leaf node | ||
return referenceMap[nodeReference]; | ||
} | ||
Object.keys(node).forEach((key) => { | ||
if (typeof node[key] === "object") { | ||
node[key] = resolveAstReferences(node[key], referenceMap); | ||
} | ||
}); | ||
return node; | ||
} | ||
function handleNodeSurroundedByCharacters(startCharacter, endCharacter) { | ||
@@ -150,5 +128,2 @@ return (location, sourceCode, commentNodes) => ({ | ||
} | ||
// We need to generate the location for a node differently based on the node | ||
// type. This object holds a String => Function mapping in order to do that. | ||
const locationGenerationHandler = {}; | ||
const identityFunction = (location) => location; | ||
@@ -164,55 +139,76 @@ // Sometimes we need to delete a location node. For example, a WhereCompoundOp | ||
const removeFunction = () => null; | ||
locationGenerationHandler[APEX_TYPES.QUERY] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.SEARCH] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.FOR_INIT] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.FOR_ENHANCED_CONTROL] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.TERNARY_EXPRESSION] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.VARIABLE_EXPRESSION] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.INNER_CLASS_MEMBER] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.INNER_INTERFACE_MEMBER] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.INNER_ENUM_MEMBER] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.METHOD_MEMBER] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.IF_ELSE_BLOCK] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.NAME_VALUE_PARAMETER] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.VARIABLE_DECLARATION] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.BINARY_EXPRESSION] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.BOOLEAN_EXPRESSION] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.ASSIGNMENT_EXPRESSION] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.FIELD_MEMBER] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.VALUE_WHEN] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.ELSE_WHEN] = identityFunction; | ||
locationGenerationHandler[APEX_TYPES.WHERE_COMPOUND_OPERATOR] = removeFunction; | ||
locationGenerationHandler[APEX_TYPES.VARIABLE_DECLARATION_STATEMENT] = | ||
identityFunction; | ||
locationGenerationHandler[APEX_TYPES.WHERE_COMPOUND_EXPRESSION] = | ||
identityFunction; | ||
locationGenerationHandler[APEX_TYPES.WHERE_OPERATION_EXPRESSION] = | ||
identityFunction; | ||
locationGenerationHandler[APEX_TYPES.SELECT_INNER_QUERY] = | ||
handleNodeSurroundedByCharacters("(", ")"); | ||
locationGenerationHandler[APEX_TYPES.ANONYMOUS_BLOCK_UNIT] = | ||
handleAnonymousUnitLocation; | ||
locationGenerationHandler[APEX_TYPES.NESTED_EXPRESSION] = | ||
handleNodeSurroundedByCharacters("(", ")"); | ||
locationGenerationHandler[APEX_TYPES.PROPERTY_MEMBER] = | ||
handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[APEX_TYPES.SWITCH_STATEMENT] = | ||
handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[APEX_TYPES.NEW_LIST_LITERAL] = | ||
handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[APEX_TYPES.NEW_SET_LITERAL] = | ||
handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[APEX_TYPES.NEW_MAP_LITERAL] = | ||
handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[APEX_TYPES.NEW_STANDARD] = | ||
handleNodeEndedWithCharacter(")"); | ||
locationGenerationHandler[APEX_TYPES.VARIABLE_DECLARATIONS] = | ||
handleNodeEndedWithCharacter(";"); | ||
locationGenerationHandler[APEX_TYPES.NEW_KEY_VALUE] = | ||
handleNodeEndedWithCharacter(")"); | ||
locationGenerationHandler[APEX_TYPES.METHOD_CALL_EXPRESSION] = | ||
handleNodeEndedWithCharacter(")"); | ||
locationGenerationHandler[APEX_TYPES.ANNOTATION] = handleAnnotationLocation; | ||
locationGenerationHandler[APEX_TYPES.METHOD_DECLARATION] = | ||
handleMethodDeclarationLocation; | ||
// We need to generate the location for a node differently based on the node | ||
// type. This object holds a String => Function mapping in order to do that. | ||
const locationGenerationHandler = { | ||
[APEX_TYPES.QUERY]: identityFunction, | ||
[APEX_TYPES.SEARCH]: identityFunction, | ||
[APEX_TYPES.FOR_INIT]: identityFunction, | ||
[APEX_TYPES.FOR_ENHANCED_CONTROL]: identityFunction, | ||
[APEX_TYPES.TERNARY_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.VARIABLE_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.INNER_CLASS_MEMBER]: identityFunction, | ||
[APEX_TYPES.INNER_INTERFACE_MEMBER]: identityFunction, | ||
[APEX_TYPES.INNER_ENUM_MEMBER]: identityFunction, | ||
[APEX_TYPES.METHOD_MEMBER]: identityFunction, | ||
[APEX_TYPES.IF_ELSE_BLOCK]: identityFunction, | ||
[APEX_TYPES.NAME_VALUE_PARAMETER]: identityFunction, | ||
[APEX_TYPES.VARIABLE_DECLARATION]: identityFunction, | ||
[APEX_TYPES.BINARY_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.BOOLEAN_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.ASSIGNMENT_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.FIELD_MEMBER]: identityFunction, | ||
[APEX_TYPES.VALUE_WHEN]: identityFunction, | ||
[APEX_TYPES.ELSE_WHEN]: identityFunction, | ||
[APEX_TYPES.WHERE_COMPOUND_OPERATOR]: removeFunction, | ||
[APEX_TYPES.VARIABLE_DECLARATION_STATEMENT]: identityFunction, | ||
[APEX_TYPES.WHERE_COMPOUND_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.WHERE_OPERATION_EXPRESSION]: identityFunction, | ||
[APEX_TYPES.SELECT_INNER_QUERY]: handleNodeSurroundedByCharacters("(", ")"), | ||
[APEX_TYPES.ANONYMOUS_BLOCK_UNIT]: handleAnonymousUnitLocation, | ||
[APEX_TYPES.NESTED_EXPRESSION]: handleNodeSurroundedByCharacters("(", ")"), | ||
[APEX_TYPES.PROPERTY_MEMBER]: handleNodeEndedWithCharacter("}"), | ||
[APEX_TYPES.SWITCH_STATEMENT]: handleNodeEndedWithCharacter("}"), | ||
[APEX_TYPES.NEW_LIST_LITERAL]: handleNodeEndedWithCharacter("}"), | ||
[APEX_TYPES.NEW_SET_LITERAL]: handleNodeEndedWithCharacter("}"), | ||
[APEX_TYPES.NEW_MAP_LITERAL]: handleNodeEndedWithCharacter("}"), | ||
[APEX_TYPES.NEW_STANDARD]: handleNodeEndedWithCharacter(")"), | ||
[APEX_TYPES.VARIABLE_DECLARATIONS]: handleNodeEndedWithCharacter(";"), | ||
[APEX_TYPES.NEW_KEY_VALUE]: handleNodeEndedWithCharacter(")"), | ||
[APEX_TYPES.METHOD_CALL_EXPRESSION]: handleNodeEndedWithCharacter(")"), | ||
[APEX_TYPES.ANNOTATION]: handleAnnotationLocation, | ||
[APEX_TYPES.METHOD_DECLARATION]: handleMethodDeclarationLocation, | ||
}; | ||
/* | ||
* Generic Depth-First Search algorithm that applies a list of functions to each | ||
* node in the tree. | ||
* Each function can hook into various parts of the DFS process: | ||
* - gatherChildrenContext: gathering contexts for children nodes. When the | ||
* children nodes are visited, they will be passed this context. | ||
* - accumulator: accumulating results from children nodes. This is run after | ||
* every individual child node is visited. | ||
* - apply: applying the function to the current node. This is run after all | ||
* children nodes have been visited. | ||
*/ | ||
function dfsPostOrderApply(node, fns, currentContexts) { | ||
const finalChildrenResults = new Array(fns.length); | ||
const childrenContexts = new Array(fns.length); | ||
for (let i = 0; i < fns.length; i++) { | ||
childrenContexts[i] = fns[i]?.gatherChildrenContext?.(node, currentContexts ? currentContexts[i] : undefined); | ||
} | ||
const keys = Object.keys(node); | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
if (typeof node[key] === "object") { | ||
const childrenResults = dfsPostOrderApply(node[key], fns, childrenContexts); | ||
for (let j = 0; j < fns.length; j++) { | ||
finalChildrenResults[j] = fns[j]?.accumulator?.(childrenResults[j], finalChildrenResults[j]); | ||
} | ||
} | ||
} | ||
const results = []; | ||
for (let i = 0; i < fns.length; i++) { | ||
results.push(fns[i]?.apply(node, finalChildrenResults[i], currentContexts ? currentContexts[i] : undefined, childrenContexts[i])); | ||
} | ||
return results; | ||
} | ||
/** | ||
@@ -225,167 +221,165 @@ * Generate and/or fix node locations, because jorje sometimes either provides | ||
* is always >= any child node end index. | ||
* @param node the node being visited. | ||
* @param sourceCode the entire source code. | ||
* @param commentNodes all the comment nodes. | ||
* @return the corrected node. | ||
*/ | ||
function handleNodeLocation(node, sourceCode, commentNodes) { | ||
let currentLocation; | ||
Object.keys(node).forEach((key) => { | ||
const value = node[key]; | ||
if (typeof value === "object") { | ||
const location = handleNodeLocation(value, sourceCode, commentNodes); | ||
if (location && currentLocation) { | ||
if (currentLocation.startIndex > location.startIndex) { | ||
currentLocation.startIndex = location.startIndex; | ||
const nodeLocationVisitor = (sourceCode, commentNodes) => ({ | ||
accumulator: (entry, accumulated) => { | ||
if (!accumulated) { | ||
return entry; | ||
} | ||
if (!entry) { | ||
return accumulated; | ||
} | ||
if (accumulated.startIndex > entry.startIndex) { | ||
accumulated.startIndex = entry.startIndex; | ||
} | ||
if (accumulated.endIndex < entry.endIndex) { | ||
accumulated.endIndex = entry.endIndex; | ||
} | ||
return accumulated; | ||
}, | ||
apply: (node, currentLocation) => { | ||
const apexClass = node["@class"]; | ||
let handlerFn; | ||
if (apexClass) { | ||
handlerFn = locationGenerationHandler[apexClass]; | ||
if (!handlerFn) { | ||
const parentClass = getParentType(apexClass); | ||
if (parentClass) { | ||
handlerFn = locationGenerationHandler[parentClass]; | ||
} | ||
if (currentLocation.endIndex < location.endIndex) { | ||
currentLocation.endIndex = location.endIndex; | ||
} | ||
} | ||
else if (location && !currentLocation) { | ||
currentLocation = location; | ||
} | ||
if (handlerFn && currentLocation) { | ||
node.loc = handlerFn(currentLocation, sourceCode, commentNodes, node); | ||
} | ||
else if (handlerFn && node.loc) { | ||
node.loc = handlerFn(node.loc, sourceCode, commentNodes, node); | ||
} | ||
const nodeLoc = node.loc; | ||
if (!nodeLoc) { | ||
delete node.loc; | ||
} | ||
else if (nodeLoc && currentLocation) { | ||
if (nodeLoc.startIndex > currentLocation.startIndex) { | ||
nodeLoc.startIndex = currentLocation.startIndex; | ||
} | ||
else { | ||
currentLocation.startIndex = nodeLoc.startIndex; | ||
} | ||
if (nodeLoc.endIndex < currentLocation.endIndex) { | ||
nodeLoc.endIndex = currentLocation.endIndex; | ||
} | ||
else { | ||
currentLocation.endIndex = nodeLoc.endIndex; | ||
} | ||
} | ||
}); | ||
const apexClass = node["@class"]; | ||
let handlerFn; | ||
if (apexClass) { | ||
if (apexClass in locationGenerationHandler) { | ||
handlerFn = locationGenerationHandler[apexClass]; | ||
if (currentLocation) { | ||
return { ...currentLocation }; | ||
} | ||
else { | ||
const separatorIndex = apexClass.indexOf("$"); | ||
if (separatorIndex !== -1) { | ||
const parentClass = apexClass.slice(0, separatorIndex); | ||
if (parentClass in locationGenerationHandler) { | ||
handlerFn = locationGenerationHandler[parentClass]; | ||
if (nodeLoc) { | ||
return { | ||
startIndex: nodeLoc.startIndex, | ||
endIndex: nodeLoc.endIndex, | ||
}; | ||
} | ||
return null; | ||
}, | ||
}); | ||
const metadataVisitor = (emptyLineLocations) => ({ | ||
apply: (node, _accumulated, context, childrenContext) => { | ||
const apexClass = node["@class"]; | ||
// #511 - If the user manually specify linebreaks in their original query, | ||
// we will use that as a heuristic to manually add hardlines to the result | ||
// query as well. | ||
if (apexClass === APEX_TYPES.SEARCH || apexClass === APEX_TYPES.QUERY) { | ||
node.forcedHardline = node.loc.startLine !== node.loc.endLine; | ||
} | ||
// jorje parses all `if` and `else if` blocks into `ifBlocks`, so we add | ||
// `ifBlockIndex` into the node for handling code to differentiate them. | ||
else if (apexClass === APEX_TYPES.IF_ELSE_BLOCK) { | ||
node.ifBlocks.forEach((ifBlock, index) => { | ||
ifBlock.ifBlockIndex = index; | ||
}); | ||
} | ||
if ("inputParameters" in node && Array.isArray(node.inputParameters)) { | ||
node.inputParameters.forEach((inputParameter) => { | ||
inputParameter.insideParenthesis = true; | ||
}); | ||
} | ||
const trailingEmptyLineAllowed = ALLOW_TRAILING_EMPTY_LINE.includes(apexClass); | ||
const nodeLoc = getNodeLocation(node); | ||
let isLastNodeInArray = false; | ||
// Here we flag the current node as the last node in the array, because | ||
// we don't want a trailing empty line after it. | ||
if (context?.arraySiblings) { | ||
isLastNodeInArray = | ||
context.arraySiblings.indexOf(node) === | ||
context.arraySiblings.length - 1; | ||
} | ||
// Here we turn off trailing empty line for a child node when its next | ||
// sibling is on the same line. | ||
// The reasoning is that for a block of code like this: | ||
// ``` | ||
// Integer a = 1; Integer c = 2; Integer c = 3; | ||
// | ||
// Integer d = 4; | ||
// ``` | ||
// We don't want a trailing empty line after `Integer a = 1;` | ||
// so we need to mark it as a special node. | ||
// We are doing this at the parent node level, because when we run the | ||
// Depth-First search, we don't have enough context at the child node level | ||
// to determine if its next sibling is on the same line or not. | ||
if (childrenContext.arraySiblings) { | ||
for (let i = 0; i < childrenContext.arraySiblings.length; i++) { | ||
const currentChild = childrenContext.arraySiblings[i]; | ||
const nextChildIndex = i + 1; | ||
if (nextChildIndex < childrenContext.arraySiblings.length) { | ||
const nextChild = childrenContext.arraySiblings[nextChildIndex]; | ||
if (currentChild.trailingEmptyLine && | ||
currentChild.loc && | ||
nextChild.loc && | ||
currentChild.loc.endLine === nextChild.loc.startLine) { | ||
currentChild.trailingEmptyLine = false; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (handlerFn && currentLocation) { | ||
node.loc = handlerFn(currentLocation, sourceCode, commentNodes, node); | ||
} | ||
else if (handlerFn && node.loc) { | ||
node.loc = handlerFn(node.loc, sourceCode, commentNodes, node); | ||
} | ||
const nodeLoc = node.loc; | ||
if (!nodeLoc) { | ||
delete node.loc; | ||
} | ||
else if (nodeLoc && currentLocation) { | ||
if (nodeLoc.startIndex > currentLocation.startIndex) { | ||
nodeLoc.startIndex = currentLocation.startIndex; | ||
if (apexClass && | ||
nodeLoc && | ||
context.allowTrailingEmptyLine && | ||
trailingEmptyLineAllowed && | ||
!isLastNodeInArray) { | ||
const nextLine = nodeLoc.endLine + 1; | ||
const nextEmptyLine = emptyLineLocations.indexOf(nextLine); | ||
if (nextEmptyLine !== -1) { | ||
node.trailingEmptyLine = true; | ||
} | ||
} | ||
else { | ||
currentLocation.startIndex = nodeLoc.startIndex; | ||
}, | ||
gatherChildrenContext: (node, currentContext) => { | ||
const apexClass = node["@class"]; | ||
let allowTrailingEmptyLineWithin; | ||
const isSpecialClass = TRAILING_EMPTY_LINE_AFTER_LAST_NODE.includes(apexClass); | ||
const trailingEmptyLineAllowed = ALLOW_TRAILING_EMPTY_LINE.includes(apexClass); | ||
if (isSpecialClass) { | ||
allowTrailingEmptyLineWithin = false; | ||
} | ||
if (nodeLoc.endIndex < currentLocation.endIndex) { | ||
nodeLoc.endIndex = currentLocation.endIndex; | ||
else if (trailingEmptyLineAllowed) { | ||
allowTrailingEmptyLineWithin = true; | ||
} | ||
else { | ||
currentLocation.endIndex = nodeLoc.endIndex; | ||
// currentContext is undefined for the root node, we hardcode | ||
// allowTrailingEmptyLine to true for it | ||
allowTrailingEmptyLineWithin = | ||
currentContext?.allowTrailingEmptyLine ?? true; | ||
} | ||
} | ||
if (currentLocation) { | ||
return { ...currentLocation }; | ||
} | ||
if (nodeLoc) { | ||
let arraySiblings; | ||
if (Array.isArray(node) && node.length > 0) { | ||
arraySiblings = node; | ||
} | ||
return { | ||
startIndex: nodeLoc.startIndex, | ||
endIndex: nodeLoc.endIndex, | ||
allowTrailingEmptyLine: allowTrailingEmptyLineWithin, | ||
arraySiblings, | ||
}; | ||
} | ||
return null; | ||
} | ||
/** | ||
* Generate extra metadata (e.g. empty lines) for nodes. | ||
* This method is called recursively while visiting each node in the tree. | ||
* | ||
* @param node the node being visited | ||
* @param emptyLineLocations a list of lines that are empty in the source code | ||
* @param allowTrailingEmptyLine whether trailing empty line is allowed | ||
* for this node. This helps when dealing with statements that contain other | ||
* statements. For example, we turn this to `false` for the block statements | ||
* inside an IfElseBlock | ||
* | ||
*/ | ||
function generateExtraMetadata(node, emptyLineLocations, allowTrailingEmptyLine) { | ||
const apexClass = node["@class"]; | ||
let allowTrailingEmptyLineWithin; | ||
const isSpecialClass = TRAILING_EMPTY_LINE_AFTER_LAST_NODE.includes(apexClass); | ||
const trailingEmptyLineAllowed = ALLOW_TRAILING_EMPTY_LINE.includes(apexClass); | ||
if (isSpecialClass) { | ||
allowTrailingEmptyLineWithin = false; | ||
} | ||
else if (trailingEmptyLineAllowed) { | ||
allowTrailingEmptyLineWithin = true; | ||
} | ||
else { | ||
allowTrailingEmptyLineWithin = allowTrailingEmptyLine; | ||
} | ||
// #511 - If the user manually specify linebreaks in their original query, | ||
// we will use that as a heuristic to manually add hardlines to the result | ||
// query as well. | ||
if (apexClass === APEX_TYPES.SEARCH || apexClass === APEX_TYPES.QUERY) { | ||
node.forcedHardline = node.loc.startLine !== node.loc.endLine; | ||
} | ||
// jorje parses all `if` and `else if` blocks into `ifBlocks`, so we add | ||
// `ifBlockIndex` into the node for handling code to differentiate them. | ||
else if (apexClass === APEX_TYPES.IF_ELSE_BLOCK) { | ||
node.ifBlocks.forEach((ifBlock, index) => { | ||
ifBlock.ifBlockIndex = index; | ||
}); | ||
} | ||
if ("inputParameters" in node && Array.isArray(node.inputParameters)) { | ||
node.inputParameters.forEach((inputParameter) => { | ||
inputParameter.insideParenthesis = true; | ||
}); | ||
} | ||
Object.keys(node).forEach((key) => { | ||
if (typeof node[key] === "object") { | ||
if (Array.isArray(node)) { | ||
const keyInt = parseInt(key, 10); | ||
if (keyInt === node.length - 1) { | ||
// @ts-expect-error ts-migrate(7015) FIXME: Element implicitly has an 'any' type because index... Remove this comment to see the full error message | ||
node[key].isLastNodeInArray = true; // So that we don't apply trailing empty line after this node | ||
} | ||
else { | ||
// Here we flag a node if its next sibling is on the same line. | ||
// The reasoning is that for a block of code like this: | ||
// ``` | ||
// Integer a = 1; Integer c = 2; Integer c = 3; | ||
// | ||
// Integer d = 4; | ||
// ``` | ||
// We don't want a trailing empty line after `Integer a = 1;` | ||
// so we need to mark it as a special node. | ||
const currentChildNode = node[keyInt]; | ||
const nextChildNode = node[keyInt + 1]; | ||
if (nextChildNode && | ||
nextChildNode.loc && | ||
currentChildNode.loc && | ||
nextChildNode.loc.startLine === currentChildNode.loc.endLine) { | ||
currentChildNode.isNextStatementOnSameLine = true; | ||
} | ||
} | ||
} | ||
generateExtraMetadata(node[key], emptyLineLocations, allowTrailingEmptyLineWithin); | ||
} | ||
}); | ||
const nodeLoc = getNodeLocation(node); | ||
if (apexClass && | ||
nodeLoc && | ||
allowTrailingEmptyLine && | ||
trailingEmptyLineAllowed && | ||
!node.isLastNodeInArray && | ||
!node.isNextStatementOnSameLine) { | ||
const nextLine = nodeLoc.endLine + 1; | ||
const nextEmptyLine = emptyLineLocations.indexOf(nextLine); | ||
if (nextEmptyLine !== -1) { | ||
node.trailingEmptyLine = true; | ||
} | ||
} | ||
return nodeLoc; | ||
} | ||
}, | ||
}); | ||
function getLineNumber(lineIndexes, charIndex) { | ||
@@ -414,30 +408,26 @@ let low = 0; | ||
// utility methods. | ||
function resolveLineIndexes(node, lineIndexes) { | ||
const nodeLoc = getNodeLocation(node); | ||
if (nodeLoc && !("startLine" in nodeLoc)) { | ||
// The location node that we manually generate do not contain startLine | ||
// information, so we will create them here. | ||
nodeLoc.startLine = | ||
nodeLoc.line ?? getLineNumber(lineIndexes, nodeLoc.startIndex); | ||
} | ||
if (nodeLoc && !("endLine" in nodeLoc)) { | ||
nodeLoc.endLine = getLineNumber(lineIndexes, nodeLoc.endIndex); | ||
// Edge case: root node | ||
if (nodeLoc.endLine < 0) { | ||
nodeLoc.endLine = lineIndexes.length - 1; | ||
const lineIndexVisitor = (lineIndexes) => ({ | ||
apply: (node) => { | ||
const nodeLoc = getNodeLocation(node); | ||
if (nodeLoc && !("startLine" in nodeLoc)) { | ||
// The location node that we manually generate do not contain startLine | ||
// information, so we will create them here. | ||
nodeLoc.startLine = | ||
nodeLoc.line ?? getLineNumber(lineIndexes, nodeLoc.startIndex); | ||
} | ||
} | ||
if (nodeLoc && !("column" in nodeLoc)) { | ||
const nodeStartLineIndex = lineIndexes[nodeLoc.startLine ?? getLineNumber(lineIndexes, nodeLoc.startIndex)]; | ||
if (nodeStartLineIndex !== undefined) { | ||
nodeLoc.column = nodeLoc.startIndex - nodeStartLineIndex; | ||
if (nodeLoc && !("endLine" in nodeLoc)) { | ||
nodeLoc.endLine = getLineNumber(lineIndexes, nodeLoc.endIndex); | ||
// Edge case: root node | ||
if (nodeLoc.endLine < 0) { | ||
nodeLoc.endLine = lineIndexes.length - 1; | ||
} | ||
} | ||
} | ||
Object.keys(node).forEach((key) => { | ||
if (typeof node[key] === "object") { | ||
node[key] = resolveLineIndexes(node[key], lineIndexes); | ||
if (nodeLoc && !("column" in nodeLoc)) { | ||
const nodeStartLineIndex = lineIndexes[nodeLoc.startLine ?? getLineNumber(lineIndexes, nodeLoc.startIndex)]; | ||
if (nodeStartLineIndex !== undefined) { | ||
nodeLoc.column = nodeLoc.startIndex - nodeStartLineIndex; | ||
} | ||
} | ||
}); | ||
return node; | ||
} | ||
}, | ||
}); | ||
// Get a map of line number to the index of its first character | ||
@@ -485,6 +475,3 @@ function getLineIndexes(sourceCode) { | ||
else if (options.apexStandaloneParser === "native") { | ||
const { path: serializerBin } = await getNativeExecutable(); | ||
if (!(await doesFileExist(serializerBin))) { | ||
throw new Error("Native executable does not exist. Please download with `npx install-apex-executables`"); | ||
} | ||
const serializerBin = await getNativeExecutableWithFallback(); | ||
const result = await parseTextWithSpawn(serializerBin, sourceCode, options.parser === "apex-anonymous"); | ||
@@ -500,3 +487,3 @@ serializedAst = result.stdout; | ||
if (serializedAst) { | ||
let ast = JSON.parse(serializedAst); | ||
const ast = JSON.parse(serializedAst); | ||
if (ast[APEX_TYPES.PARSER_OUTPUT] && | ||
@@ -507,15 +494,11 @@ ast[APEX_TYPES.PARSER_OUTPUT].parseErrors.length > 0) { | ||
} | ||
const commentNodes = ast[APEX_TYPES.PARSER_OUTPUT].hiddenTokenMap | ||
ast.comments = ast[APEX_TYPES.PARSER_OUTPUT].hiddenTokenMap | ||
.map((item) => item[1]) | ||
.filter((node) => node["@class"] === APEX_TYPES.BLOCK_COMMENT || | ||
node["@class"] === APEX_TYPES.INLINE_COMMENT); | ||
ast = resolveAstReferences(ast, {}); | ||
handleNodeLocation(ast, sourceCode, commentNodes); | ||
const lineIndexes = getLineIndexes(sourceCode); | ||
ast = resolveLineIndexes(ast, lineIndexes); | ||
generateExtraMetadata(ast, getEmptyLineLocations(sourceCode), true); | ||
ast.comments = ast[APEX_TYPES.PARSER_OUTPUT].hiddenTokenMap | ||
.map((token) => token[1]) | ||
.filter((node) => node["@class"] === APEX_TYPES.INLINE_COMMENT || | ||
node["@class"] === APEX_TYPES.BLOCK_COMMENT); | ||
dfsPostOrderApply(ast, [ | ||
nodeLocationVisitor(sourceCode, ast.comments), | ||
lineIndexVisitor(getLineIndexes(sourceCode)), | ||
metadataVisitor(getEmptyLineLocations(sourceCode)), | ||
]); | ||
return ast; | ||
@@ -522,0 +505,0 @@ } |
@@ -6,2 +6,3 @@ import { AstPath } from "prettier"; | ||
[APEX_TYPES.PARSER_OUTPUT]: jorje.ParserOutput; | ||
comments: jorje.HiddenToken[]; | ||
}; | ||
@@ -19,4 +20,2 @@ export type GenericComment = jorje.HiddenToken; | ||
trailingEmptyLine?: boolean; | ||
isNextStatementOnSameLine?: boolean; | ||
isLastNodeInArray?: boolean; | ||
}; | ||
@@ -52,11 +51,11 @@ export type AnnotatedComment = AnnotatedAstNode & GenericComment & { | ||
export declare function findNextUncommentedCharacter(sourceCode: string, character: string, fromIndex: number, commentNodes: GenericComment[], backwards?: boolean): number; | ||
export declare function getParentType(type: string): string | undefined; | ||
export declare function getPrecedence(op: string): number; | ||
export declare function doesFileExist(file: string): Promise<boolean>; | ||
export declare function getSerializerBinDirectory(): Promise<string>; | ||
interface NativeExecutable { | ||
path: string; | ||
filename: string; | ||
version: string; | ||
} | ||
export declare function getNativeExecutable(): Promise<NativeExecutable>; | ||
export declare const NATIVE_PACKAGES: Record<string, string>; | ||
export declare const NATIVE_EXECUTABLE_NAME: string; | ||
export declare const JAVA_EXECUTABLE_NAME: string; | ||
export declare function getNativeExecutableNameForPlatform(fullPlatform: string): string; | ||
export declare function getNativeExecutableWithFallback(): Promise<string>; | ||
export {}; |
@@ -5,3 +5,3 @@ /* eslint no-param-reassign: 0 */ | ||
import * as url from "node:url"; | ||
import { APEX_TYPES, APEX_TYPES as apexTypes } from "./constants.js"; | ||
import { APEX_TYPES, APEX_TYPES as apexTypes, DATA_CATEGORY, MODIFIER, ORDER, ORDER_NULL, QUERY, QUERY_WHERE, } from "./constants.js"; | ||
export function isBinaryish(node) { | ||
@@ -71,5 +71,2 @@ return (node["@class"] === apexTypes.BOOLEAN_EXPRESSION || | ||
"trailingEmptyLine", | ||
"isLastNodeInArray", | ||
"numberOfDottedExpressions", | ||
"isNextStatementOnSameLine", | ||
"forcedHardline", | ||
@@ -151,2 +148,23 @@ ]; | ||
} | ||
// Optimization to look up parent types faster | ||
const PARENT_TYPES = [ | ||
...Object.values(APEX_TYPES), | ||
...Object.keys(DATA_CATEGORY), | ||
...Object.keys(MODIFIER), | ||
...Object.keys(QUERY), | ||
...Object.keys(QUERY_WHERE), | ||
...Object.keys(ORDER), | ||
...Object.keys(ORDER_NULL), | ||
] | ||
.filter((type) => type.includes("$")) | ||
.reduce((acc, type) => { | ||
const [parentType] = type.split("$"); | ||
if (parentType) { | ||
acc[type] = parentType; | ||
} | ||
return acc; | ||
}, {}); | ||
export function getParentType(type) { | ||
return PARENT_TYPES[type]; | ||
} | ||
// One big difference between our precedence list vs Prettier's core | ||
@@ -206,14 +224,26 @@ // is that == (and its precedence equivalences) has the same precedence | ||
} | ||
export async function getNativeExecutable() { | ||
export const NATIVE_PACKAGES = { | ||
"darwin-x64": "@prettier-apex/apex-ast-serializer-darwin-x64", | ||
"darwin-arm64": "@prettier-apex/apex-ast-serializer-darwin-arm64", | ||
"linux-x64": "@prettier-apex/apex-ast-serializer-linux-x64", | ||
"win32-x64": "@prettier-apex/apex-ast-serializer-win32-x64", | ||
}; | ||
export const NATIVE_EXECUTABLE_NAME = `apex-ast-serializer-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`; | ||
export const JAVA_EXECUTABLE_NAME = `apex-ast-serializer${process.platform === "win32" ? ".bat" : ""}`; | ||
export function getNativeExecutableNameForPlatform(fullPlatform) { | ||
return `apex-ast-serializer-${fullPlatform}${fullPlatform.startsWith("win32") ? ".exe" : ""}`; | ||
} | ||
export async function getNativeExecutableWithFallback() { | ||
const { arch, platform } = process; | ||
// This will be bumped automatically when we run the release script for new versions | ||
const version = "2.1.5"; | ||
const filename = `apex-ast-serializer-${version}-${platform}-${arch}${platform === "win32" ? ".exe" : ""}`; | ||
const serializerBin = await getSerializerBinDirectory(); | ||
const executableBin = nodePath.relative(process.cwd(), nodePath.join(serializerBin, "..", "..", filename)); | ||
return { | ||
version, | ||
path: executableBin, | ||
filename, | ||
}; | ||
const packageName = NATIVE_PACKAGES[`${platform}-${arch}`]; | ||
try { | ||
if (!packageName) { | ||
throw new Error("No prebuilt binary available for this platform"); | ||
} | ||
const nativeBin = nodePath.join(packageName, NATIVE_EXECUTABLE_NAME); | ||
return require.resolve(nativeBin); | ||
} | ||
catch (e) { | ||
return nodePath.join(await getSerializerBinDirectory(), JAVA_EXECUTABLE_NAME); | ||
} | ||
} |
{ | ||
"name": "prettier-plugin-apex", | ||
"version": "2.1.5", | ||
"version": "2.2.0-beta.3", | ||
"description": "Salesforce Apex plugin for Prettier", | ||
@@ -22,21 +22,2 @@ "type": "module", | ||
], | ||
"scripts": { | ||
"build": "rimraf dist && tsc --project tsconfig.prod.json && pnpm run build:standalone", | ||
"build:dev": "tsc", | ||
"build:standalone": "vite build", | ||
"clean": "rimraf node_modules dist", | ||
"pretest": "pnpm run lint", | ||
"test": "vitest run", | ||
"test:parser:built-in": "cross-env APEX_PARSER=built-in pnpm test", | ||
"test:parser:native": "cross-env APEX_PARSER=native pnpm test", | ||
"test:package": "tsx tests_integration/package/run-package-tests.mts", | ||
"start-server": "tsx bin/start-apex-server.ts -h 0.0.0.0 -c '*'", | ||
"wait-server": "wait-on --timeout 300000 http://localhost:2117/api/ast/", | ||
"stop-server": "tsx bin/stop-apex-server.ts", | ||
"lint": "eslint \"{src,bin,tests_config}/**/*.{js,ts}\"", | ||
"prettier": "prettier --write \"{bin,src,playground,tests_config}/**/*.{js,mjs,ts,tsx,css,html}\" \"./*.{md,mjs,ts}\"", | ||
"prepack": "pnpm run build", | ||
"debug-check:native": "prettier --no-config --apex-standalone-parser native --debug-check --plugin=./dist/src/index.js", | ||
"debug-check:built-in": "prettier --apex-standalone-parser built-in --apex-standalone-port 2117 --debug-check --plugin=./dist/src/index.js" | ||
}, | ||
"keywords": [ | ||
@@ -49,11 +30,7 @@ "apex", | ||
"devDependencies": { | ||
"@tsconfig/node18": "18.2.4", | ||
"@types/node": "18.19.31", | ||
"@types/wait-on": "5.3.4", | ||
"@vitest/coverage-v8": "2.1.2", | ||
"@vitest/coverage-v8": "2.1.3", | ||
"rimraf": "5.0.10", | ||
"tsx": "4.19.1", | ||
"typescript": "5.6.2", | ||
"vite": "5.4.8", | ||
"vitest": "2.1.2" | ||
"vite": "5.4.10", | ||
"vitest": "2.1.3" | ||
}, | ||
@@ -67,2 +44,8 @@ "peerDependencies": { | ||
}, | ||
"optionalDependencies": { | ||
"@prettier-apex/apex-ast-serializer-darwin-arm64": "2.2.0-beta.3", | ||
"@prettier-apex/apex-ast-serializer-linux-x64": "2.2.0-beta.3", | ||
"@prettier-apex/apex-ast-serializer-win32-x64": "2.2.0-beta.3", | ||
"@prettier-apex/apex-ast-serializer-darwin-x64": "2.2.0-beta.3" | ||
}, | ||
"nx": { | ||
@@ -111,3 +94,21 @@ "implicitDependencies": [ | ||
} | ||
}, | ||
"scripts": { | ||
"build": "rimraf dist && tsc --project tsconfig.prod.json && pnpm run build:standalone", | ||
"build:dev": "tsc", | ||
"build:standalone": "vite build", | ||
"clean": "rimraf node_modules dist", | ||
"pretest": "pnpm run lint", | ||
"test": "vitest run", | ||
"test:parser:built-in": "cross-env APEX_PARSER=built-in pnpm test", | ||
"test:parser:native": "cross-env APEX_PARSER=native pnpm test", | ||
"test:package": "tsx tests_integration/package/run-package-tests.zx.mts", | ||
"start-server": "tsx bin/start-apex-server.ts -h 0.0.0.0 -c '*'", | ||
"wait-server": "wait-on --timeout 300000 http://localhost:2117/api/ast/", | ||
"stop-server": "tsx bin/stop-apex-server.ts", | ||
"lint": "eslint \"{src,bin,tests_config}/**/*.{js,ts}\"", | ||
"prettier": "prettier --write \"{bin,src,playground,tests_config}/**/*.{js,mjs,ts,tsx,css,html}\" \"./*.{md,mjs,ts}\"", | ||
"debug-check:native": "prettier --no-config --apex-standalone-parser native --debug-check --plugin=./dist/src/index.js", | ||
"debug-check:built-in": "prettier --apex-standalone-parser built-in --apex-standalone-port 2117 --debug-check --plugin=./dist/src/index.js" | ||
} | ||
} | ||
} |
@@ -29,3 +29,8 @@ # Prettier Apex | ||
- Node >= 18.11.0 (that have not been End-of-Life'd) | ||
- Java Runtime Engine >= 11 | ||
- Java Runtime Engine >= 11 _only_ for platforms without prebuilt native executables | ||
- The following platforms are provided with prebuilt native executables for improved parsing performance: | ||
- Windows x64 | ||
- Linux x64 | ||
- macOS M-based (ARM64) | ||
- macOS Intel-based (x64) | ||
@@ -137,14 +142,14 @@ ### How to use | ||
| Name | Default | Description | | ||
| ------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)) | | ||
| `tabWidth` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)) | | ||
| `useTabs` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tabs)) | | ||
| `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)) | | ||
| `insertPragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#insert-pragma)) | | ||
| `apexInsertFinalNewline` | `true` | Whether a newline is added as the last thing in the output | | ||
| `apexStandaloneParser` | `none` | If set to `built-in`, Prettier uses the built in standalone parser for better performance. See [Using built-in HTTP Server](#-using-built-in-http-server).<br>If set to `none`, Prettier invokes the CLI parser for every file.<br>If set to `native`, Prettier uses the native executables to speed up the parsing process. See [Using native executables](#-using-native-executables-experimental). | | ||
| `apexStandalonePort` | `2117` | The port that the standalone Apex parser listens on.<br>Only applicable if `apexStandaloneParser` is `built-in`. | | ||
| `apexStandaloneHost` | `localhost` | The host that the standalone Apex parser listens on.<br>Only applicable if `apexStandaloneParser` is `built-in`. | | ||
| `apexStandaloneProtocol` | `http` | The protocol that the standalone Apex parser uses.<br>Only applicable if `apexStandaloneParser` is `built-in`. | | ||
| Name | Default | Description | | ||
| ------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `printWidth` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)) | | ||
| `tabWidth` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)) | | ||
| `useTabs` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tabs)) | | ||
| `requirePragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#require-pragma)) | | ||
| `insertPragma` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#insert-pragma)) | | ||
| `apexInsertFinalNewline` | `true` | Whether a newline is added as the last thing in the output | | ||
| `apexStandaloneParser` | `native` | If set to `built-in`, Prettier uses the built in standalone parser for better performance. See [Using built-in HTTP Server](#-using-built-in-http-server).<br>If set to `none`, Prettier invokes the Java CLI parser for every file.<br>If set to `native`, Prettier uses the native executables to speed up the parsing process, with fallback to the Java CLI parser. | | ||
| `apexStandalonePort` | `2117` | The port that the standalone Apex parser listens on.<br>Only applicable if `apexStandaloneParser` is `built-in`. | | ||
| `apexStandaloneHost` | `localhost` | The host that the standalone Apex parser listens on.<br>Only applicable if `apexStandaloneParser` is `built-in`. | | ||
| `apexStandaloneProtocol` | `http` | The protocol that the standalone Apex parser uses.<br>Only applicable if `apexStandaloneParser` is `built-in`. | | ||
@@ -161,2 +166,4 @@ ## ๐ Editor integration | ||
> โผ๏ธ Prettier Apex is now shipped with native executables for most popular platforms that significantly enhance the parsing performance. You should try this default configuration first before considering the built-in HTTP server. | ||
By default, | ||
@@ -197,32 +204,16 @@ this library invokes a CLI application to get the AST of the Apex code. | ||
### ๐งช Using native executables (EXPERIMENTAL) | ||
## ๐ข Continuous Integration | ||
Since version `2.1.0`, we have added support for using native executables to speed up the parsing process. | ||
This is an experimental feature and is not enabled by default. | ||
This method is faster than calling the parser via Java, but slower than the built-in HTTP server. | ||
It is only available for the following platforms: | ||
Prettier Apex can be used to automatically check correct formatting for Apex code | ||
in the context of CI/CD, for example: | ||
- Windows x64 | ||
- Linux x64 | ||
- macOS M-based (ARM64) | ||
### Using default configuration | ||
In order to use it, first run: | ||
```bash | ||
# Download the native executable for your architecture. It will fail if your platform is not supported. | ||
npx install-apex-executables | ||
prettier --plugin=prettier-plugin-apex --check 'project/**/*.{trigger,cls}' | ||
``` | ||
Then, you can use the `--apex-standalone-parser native` flag to use the native executables (or configure it via `.prettierrc`): | ||
### Using built-in HTTP Server | ||
```bash | ||
prettier --apex-standalone-parser native --write "/path/to/project/**/*.{trigger,cls}" | ||
``` | ||
## ๐ข Continuous Integration | ||
Prettier Apex can be used to automatically check correct formatting for Apex code | ||
in the context of CI/CD, for example: | ||
```bash | ||
# Start the language server for improved parsing performance, | ||
@@ -229,0 +220,0 @@ # and put it in the background (*nix only) so that next commands can be run. |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
25231472
5
4
7
78
7485
3
225
6