prettier-plugin-apex
Advanced tools
Comparing version 1.0.0-alpha.9 to 1.0.0-beta.1
## Unreleased | ||
## 1.0.0-beta.1 | ||
- Add support for Anonymous Code block with `--apex-anonymous` option. | ||
- CLI/Option change: | ||
- `use-standalone-server` option is now `apex-standalone-parser`, | ||
and it is now a choice between `none` and `built-in`. | ||
- `server-port` option is now `apex-standalone-port`. | ||
- Add `apex-verify-ast` option. | ||
- Fix dangling comments being printed incorrectly for Triggers ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/13)). | ||
Thanks to @praksb, @ntotten and @vazexqi for their help on getting jorje fixed. | ||
- Fix SOQL unary expression not generating space before next expression. | ||
- Add support for SOQL WHERE Calculation Expression ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/34)). | ||
- Add support for parameter modifiers ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/37)). | ||
- Add support for `while` loop without body ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/33)). | ||
- Add support for `for` loop without body ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/36)). | ||
- Add support for Java expressions/typerefs ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/35)). | ||
- Fix Package Version Expression. | ||
- Fix Unicode characters being printed incorrectly ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/51)). | ||
- Workaround for certain cases of AST verification failing ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/38)). | ||
- Fix overlapping node with comment when comment contains special characters. | ||
- Fix awkward breaks for long method call chain ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/53)), | ||
- Remove breaks in Map types ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/57)). | ||
- Fix binary/boolean expressions breaking after operation despite available space ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/40)). | ||
- Fix leading comment to SOQL inner query being misindentified as trailing comment to previous column clause ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/58)). | ||
- Fix awkward breaks for multiline binaryish expressions ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/21)). | ||
- Fix formatting for Apex types containing expressions but do not add groups and/or breaks. | ||
- Fix SOQL/SOSL boolean expressions having extra parentheses ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/56)). | ||
- Fix ternary expressions not breaking correctly ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/29)). | ||
- Fix comments not being indented in binaryish expressions ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/14)). | ||
- Fix array index indentation surrounding variable expressions and method call expressions. | ||
- Fix unstable IfBlock trailing comments. | ||
- Fix unstable NameValueParameter trailing comments. | ||
- Fix unstable WhereOpExpr trailing comments. | ||
## 1.0.0-alpha.9 | ||
- Fix trailing comments after class names not being printed ([issue](https://github.com/dangmai/prettier-plugin-apex/issues/20)). | ||
@@ -3,0 +38,0 @@ - Add new lines in empty blocks for Enum. |
{ | ||
"name": "prettier-plugin-apex", | ||
"version": "1.0.0-alpha.9", | ||
"version": "1.0.0-beta.1", | ||
"description": "Salesforce Apex plugin for Prettier", | ||
@@ -18,2 +18,3 @@ "main": "src/index.js", | ||
"scripts": { | ||
"coverage": "codecov", | ||
"pretest": "npm run lint", | ||
@@ -23,3 +24,2 @@ "test": "jest", | ||
"stop-server": "node bin/stop-apex-server.js", | ||
"execute": "node src/execute.js", | ||
"lint": "eslint \"{src,tests_config}/**/*.js\"", | ||
@@ -35,2 +35,3 @@ "prettier": "prettier --write \"{src,tests_config}/**/*.js\"" | ||
"devDependencies": { | ||
"codecov": "^3.5.0", | ||
"eslint": "^5.15.1", | ||
@@ -37,0 +38,0 @@ "eslint-config-airbnb-base": "^13.1.0", |
@@ -1,2 +0,2 @@ | ||
# Prettier Apex [![Build Status](https://travis-ci.org/dangmai/prettier-plugin-apex.svg)](https://travis-ci.org/dangmai/prettier-plugin-apex) [![npm](https://img.shields.io/npm/v/prettier-plugin-apex.svg)](https://www.npmjs.com/package/prettier-plugin-apex) | ||
# Prettier Apex [![Build Status](https://travis-ci.org/dangmai/prettier-plugin-apex.svg)](https://travis-ci.org/dangmai/prettier-plugin-apex) [![npm](https://img.shields.io/npm/v/prettier-plugin-apex.svg)](https://www.npmjs.com/package/prettier-plugin-apex) [![codecov](https://codecov.io/gh/dangmai/prettier-plugin-apex/branch/master/graph/badge.svg)](https://codecov.io/gh/dangmai/prettier-plugin-apex) [![Join the chat at https://gitter.im/prettier-plugin-apex/community](https://badges.gitter.im/prettier-plugin-apex/community.svg)](https://gitter.im/prettier-plugin-apex/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
@@ -60,2 +60,38 @@ ![Prettier Banner](https://raw.githubusercontent.com/prettier/prettier-logo/master/images/prettier-banner-light.png) | ||
### Tip | ||
#### Initial run | ||
If you are formatting a big code base for the first time, | ||
please make sure that you have some form of version control in place, | ||
so that you can revert any change if necessary. | ||
You should also run Prettier with the `--apex-verify-ast` argument. For example: | ||
```bash | ||
prettier --write "/path/to/project/**/*.{trigger,cls}" --apex-verify-ast | ||
``` | ||
This will guarantee that the behavior of your code did not change because of | ||
the format. | ||
Note that this argument does degrade performance, so after the initial commit | ||
feel free to stop using it in your day to day operation, provided that you only | ||
format a small amount of code each time (for example, on a file save). | ||
#### Anonymous Apex | ||
You can also format anonymous Apex with this program by using the | ||
`--apex-anonymous` flag. | ||
For example: | ||
```bash | ||
prettier --write "/path/to/project/anonymous/**/*.cls" --apex-anonymous | ||
``` | ||
Note that Prettier will treat any Apex file that it finds using the glob above | ||
as anonymous code blocks, | ||
so it is recommended that you collect all of your anonymous Apex files into | ||
one directory and limit the use of `--apex-anonymous` only in that directory. | ||
### Configuration | ||
@@ -103,3 +139,3 @@ | ||
# In a separate console | ||
prettier --use-standalone-server --write "/path/to/project/**/*.{trigger,cls}" | ||
prettier --apex-standalone-parser built-in --write "/path/to/project/**/*.{trigger,cls}" | ||
@@ -106,0 +142,0 @@ # After you are done, stop the server (if installed globally) |
@@ -10,5 +10,5 @@ /* eslint no-param-reassign: 0, no-plusplus: 0, no-else-return: 0, consistent-return: 0 */ | ||
const childNodesCacheKey = require("private").makeUniqueKey(); | ||
const values = require("./values"); | ||
const constants = require("./constants"); | ||
const apexNames = values.APEX_NAMES; | ||
const apexTypes = constants.APEX_TYPES; | ||
@@ -52,7 +52,7 @@ function getSortedChildNodes(node, resultArray) { | ||
// that difference. | ||
if (ast[apexNames.PARSER_OUTPUT].unit.loc) { | ||
return ast[apexNames.PARSER_OUTPUT].unit.loc; | ||
if (ast[apexTypes.PARSER_OUTPUT].unit.loc) { | ||
return ast[apexTypes.PARSER_OUTPUT].unit.loc; | ||
} | ||
if (ast[apexNames.PARSER_OUTPUT].unit.body.loc) { | ||
return ast[apexNames.PARSER_OUTPUT].unit.body.loc; | ||
if (ast[apexTypes.PARSER_OUTPUT].unit.body.loc) { | ||
return ast[apexTypes.PARSER_OUTPUT].unit.body.loc; | ||
} | ||
@@ -68,3 +68,3 @@ throw new Error( | ||
if (comment.location.endIndex < getRootNodeLocation(ast).startIndex) { | ||
comment.followingNode = ast[apexNames.PARSER_OUTPUT].unit; | ||
comment.followingNode = ast[apexTypes.PARSER_OUTPUT].unit; | ||
return; | ||
@@ -94,6 +94,6 @@ } | ||
const declarationUnits = [ | ||
apexNames.CLASS_DECLARATION, | ||
apexNames.TRIGGER_DECLARATION_UNIT, | ||
apexNames.ENUM_DECLARATION, | ||
apexNames.INTERFACE_DECLARATION, | ||
apexTypes.CLASS_DECLARATION, | ||
apexTypes.TRIGGER_DECLARATION_UNIT, | ||
apexTypes.ENUM_DECLARATION, | ||
apexTypes.INTERFACE_DECLARATION, | ||
]; | ||
@@ -226,8 +226,8 @@ if ( | ||
function attach(ast, sourceCode) { | ||
const comments = ast[apexNames.PARSER_OUTPUT].hiddenTokenMap | ||
const comments = ast[apexTypes.PARSER_OUTPUT].hiddenTokenMap | ||
.map(item => item[1]) | ||
.filter( | ||
item => | ||
item["@class"] === apexNames.INLINE_COMMENT || | ||
item["@class"] === apexNames.BLOCK_COMMENT, | ||
item["@class"] === apexTypes.INLINE_COMMENT || | ||
item["@class"] === apexTypes.BLOCK_COMMENT, | ||
); | ||
@@ -237,3 +237,3 @@ const tiesToBreak = []; | ||
comments.forEach(comment => { | ||
decorateComment(ast[apexNames.PARSER_OUTPUT].unit, comment, ast); | ||
decorateComment(ast[apexTypes.PARSER_OUTPUT], comment, ast); | ||
@@ -261,3 +261,3 @@ const pn = comment.precedingNode; | ||
} else if (pn) { | ||
if (en && en["@class"] === apexNames.BLOCK_STATEMENT && !fn) { | ||
if (en && en["@class"] === apexTypes.BLOCK_STATEMENT && !fn) { | ||
// Special case: this is a trailing comment in a block statement | ||
@@ -350,6 +350,6 @@ breakTies(tiesToBreak, sourceCode); | ||
// from prettier to buffer the output | ||
if (comment["@class"] === apexNames.INLINE_COMMENT) { | ||
if (comment["@class"] === apexTypes.INLINE_COMMENT) { | ||
if ( | ||
(leadingSpace.length > 0 && numberOfNewLines === 0) || | ||
parentNode["@class"] === apexNames.LOCATION_IDENTIFIER | ||
parentNode["@class"] === apexTypes.LOCATION_IDENTIFIER | ||
) { | ||
@@ -393,3 +393,3 @@ parts.push(lineSuffix(concat([" ", print(commentPath)]))); | ||
} | ||
if (comment["@class"] === apexNames.INLINE_COMMENT) { | ||
if (comment["@class"] === apexTypes.INLINE_COMMENT) { | ||
parts.push(lineSuffix(print(commentPath))); | ||
@@ -403,11 +403,5 @@ } else { | ||
function allowTrailingComments(apexClass) { | ||
const allowedTypes = [ | ||
apexNames.CLASS_DECLARATION, | ||
apexNames.INTERFACE_DECLARATION, | ||
apexNames.METHOD_DECLARATION, | ||
apexNames.ENUM_DECLARATION, | ||
apexNames.VARIABLE_DECLARATION, | ||
apexNames.LOCATION_IDENTIFIER, | ||
]; | ||
let trailingCommentsAllowed = allowedTypes.includes(apexClass); | ||
let trailingCommentsAllowed = constants.ALLOW_TRAILING_COMMENT.includes( | ||
apexClass, | ||
); | ||
const separatorIndex = apexClass.indexOf("$"); | ||
@@ -417,3 +411,3 @@ if (separatorIndex !== -1) { | ||
trailingCommentsAllowed = | ||
trailingCommentsAllowed || parentClass === apexNames.STATEMENT; | ||
trailingCommentsAllowed || parentClass === apexTypes.STATEMENT; | ||
} | ||
@@ -444,3 +438,3 @@ return trailingCommentsAllowed; | ||
allowTrailingComments(value["@class"]) || | ||
comment["@class"] === apexNames.BLOCK_COMMENT | ||
comment["@class"] === apexTypes.BLOCK_COMMENT | ||
)) | ||
@@ -447,0 +441,0 @@ ) { |
const parse = require("./parser"); | ||
const print = require("./printer"); | ||
const { massageAstNode } = require("./util"); | ||
@@ -31,2 +32,3 @@ const languages = [ | ||
locEnd, | ||
preprocess: text => text.trim(), | ||
}, | ||
@@ -38,2 +40,3 @@ }; | ||
print, | ||
massageAstNode, | ||
}, | ||
@@ -43,10 +46,20 @@ }; | ||
const options = { | ||
useStandaloneServer: { | ||
type: "boolean", | ||
apexStandaloneParser: { | ||
type: "choice", | ||
category: "Global", | ||
default: false, | ||
default: "none", | ||
choices: [ | ||
{ | ||
value: "none", | ||
description: "Do not use a standalone parser", | ||
}, | ||
{ | ||
value: "built-in", | ||
description: "Use the built in standalone parser", | ||
}, | ||
], | ||
description: | ||
"Use a standalone server to speed up the parsing process. This server needs to be started and stopped separately from the Prettier process", | ||
"Use a standalone process to speed up parsing. This process needs to be started and stopped separately from the Prettier process", | ||
}, | ||
serverPort: { | ||
apexStandalonePort: { | ||
type: "int", | ||
@@ -56,4 +69,17 @@ category: "Global", | ||
description: | ||
"The standalone server port to connect to. Only applicable if useStandaloneServer is true", | ||
"The standalone server port to connect to. Only applicable if apexStandaloneParser is true", | ||
}, | ||
apexVerifyAst: { | ||
type: "boolean", | ||
category: "Global", | ||
default: false, | ||
description: | ||
"Verify that the abstract syntax trees for the formatted code is the same as the unformatted code. This heavily degrades performance, but is recommended for initial runs on big code bases", | ||
}, | ||
apexAnonymous: { | ||
type: "boolean", | ||
category: "Global", | ||
default: false, | ||
description: "Treat the code as anonymous Apex", | ||
}, | ||
}; | ||
@@ -60,0 +86,0 @@ |
@@ -9,5 +9,10 @@ const { argv } = require("yargs"); | ||
port: argv.p || 2113, | ||
anonymous: argv.n || false, | ||
}; | ||
const args = ["-f", "json", "-i"]; | ||
if (options.anonymous) { | ||
args.push("-a"); | ||
delete options.anonymous; | ||
} | ||
@@ -14,0 +19,0 @@ const nail = nailgunClient.exec("net.dangmai.serializer.Apex", args, options); |
@@ -8,7 +8,8 @@ /* eslint no-param-reassign: 0 no-underscore-dangle: 0 */ | ||
const attachComments = require("./comments").attach; | ||
const values = require("./values"); | ||
const constants = require("./constants"); | ||
const { findNextUncommentedCharacter } = require("./util"); | ||
const apexNames = values.APEX_NAMES; | ||
const apexTypes = constants.APEX_TYPES; | ||
function parseTextWithSpawn(text) { | ||
function parseTextWithSpawn(text, anonymous) { | ||
let serializerBin = path.join(__dirname, "../vendor/apex-ast-serializer/bin"); | ||
@@ -20,3 +21,7 @@ if (process.platform === "win32") { | ||
} | ||
const executionResult = spawnSync(serializerBin, ["-f", "json", "-i"], { | ||
const args = ["-f", "json", "-i"]; | ||
if (anonymous) { | ||
args.push("-a"); | ||
} | ||
const executionResult = spawnSync(serializerBin, args, { | ||
input: text, | ||
@@ -34,5 +39,8 @@ }); | ||
function parseTextWithNailgun(text, serverPort) { | ||
function parseTextWithNailgun(text, serverPort, anonymous) { | ||
const ngClientLocation = path.join(__dirname, "ng-client.js"); | ||
const args = [ngClientLocation, "-a", "localhost", "-p", serverPort]; | ||
if (anonymous) { | ||
args.push("-n"); | ||
} | ||
const executionResult = childProcess.spawnSync(process.argv[0], args, { | ||
@@ -83,14 +91,98 @@ input: text, | ||
function handleInnerQueryLocation(location, sourceCode, commentNodes) { | ||
const resultLocation = {}; | ||
resultLocation.startIndex = findNextUncommentedCharacter( | ||
sourceCode, | ||
"(", | ||
location.startIndex, | ||
commentNodes, | ||
/* backwards */ true, | ||
); | ||
resultLocation.endIndex = findNextUncommentedCharacter( | ||
sourceCode, | ||
")", | ||
location.startIndex, | ||
commentNodes, | ||
/* backwards */ false, | ||
); | ||
return resultLocation; | ||
} | ||
function handleNodeEndedWithCharacter(endCharacter) { | ||
return (location, sourceCode, commentNodes) => { | ||
const resultLocation = {}; | ||
resultLocation.startIndex = location.startIndex; | ||
resultLocation.endIndex = findNextUncommentedCharacter( | ||
sourceCode, | ||
endCharacter, | ||
location.endIndex, | ||
commentNodes, | ||
/* backwards */ false, | ||
); | ||
return resultLocation; | ||
}; | ||
} | ||
function handleAnonymousUnitLocation(location, sourceCode) { | ||
return { | ||
startIndex: 0, | ||
endIndex: sourceCode.length, | ||
}; | ||
} | ||
// 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; | ||
locationGenerationHandler[apexTypes.QUERY] = identityFunction; | ||
locationGenerationHandler[apexTypes.VARIABLE_EXPRESSION] = identityFunction; | ||
locationGenerationHandler[apexTypes.INNER_CLASS_MEMBER] = identityFunction; | ||
locationGenerationHandler[apexTypes.INNER_INTERFACE_MEMBER] = identityFunction; | ||
locationGenerationHandler[apexTypes.INNER_ENUM_MEMBER] = identityFunction; | ||
locationGenerationHandler[apexTypes.METHOD_MEMBER] = identityFunction; | ||
locationGenerationHandler[apexTypes.IF_ELSE_BLOCK] = identityFunction; | ||
locationGenerationHandler[apexTypes.NAME_VALUE_PARAMETER] = identityFunction; | ||
locationGenerationHandler[ | ||
apexTypes.WHERE_OPERATION_EXPRESSION | ||
] = identityFunction; | ||
locationGenerationHandler[ | ||
apexTypes.SELECT_INNER_QUERY | ||
] = handleInnerQueryLocation; | ||
locationGenerationHandler[ | ||
apexTypes.ANONYMOUS_BLOCK_UNIT | ||
] = handleAnonymousUnitLocation; | ||
locationGenerationHandler[ | ||
apexTypes.PROPERTY_MEMBER | ||
] = handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[ | ||
apexTypes.SWITCH_STATEMENT | ||
] = handleNodeEndedWithCharacter("}"); | ||
locationGenerationHandler[ | ||
apexTypes.VARIABLE_DECLARATION_STATEMENT | ||
] = handleNodeEndedWithCharacter(";"); | ||
locationGenerationHandler[ | ||
apexTypes.FIELD_MEMBER | ||
] = handleNodeEndedWithCharacter(";"); | ||
locationGenerationHandler[ | ||
apexTypes.NEW_KEY_VALUE | ||
] = handleNodeEndedWithCharacter(")"); | ||
locationGenerationHandler[apexTypes.QUERY] = handleNodeEndedWithCharacter("]"); | ||
/** | ||
* Sometimes jorje lies about a node location, so we will fix it here before | ||
* using that information. We do it by enforcing that a parent node start | ||
* Generate and/or fix node locations, because jorje sometimes either provides | ||
* wrong location information or a node, or doesn't provide any information at | ||
* all. | ||
* We will fix it here by enforcing that a parent node start | ||
* index is always <= any child node start index, and a parent node end index | ||
* 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 fixNodeLocation(node) { | ||
function handleNodeLocation(node, sourceCode, commentNodes) { | ||
let currentLocation; | ||
Object.keys(node).forEach(key => { | ||
if (typeof node[key] === "object") { | ||
const location = fixNodeLocation(node[key]); | ||
const location = handleNodeLocation(node[key], sourceCode, commentNodes); | ||
if (location && currentLocation) { | ||
@@ -109,2 +201,11 @@ if (currentLocation.startIndex > location.startIndex) { | ||
}); | ||
const apexClass = node["@class"]; | ||
if (apexClass && apexClass in locationGenerationHandler && currentLocation) { | ||
node.loc = locationGenerationHandler[apexClass]( | ||
currentLocation, | ||
sourceCode, | ||
commentNodes, | ||
); | ||
} | ||
if (node.loc && currentLocation) { | ||
@@ -123,3 +224,3 @@ if (node.loc.startIndex > currentLocation.startIndex) { | ||
if (currentLocation) { | ||
return currentLocation; | ||
return Object.assign({}, currentLocation); | ||
} | ||
@@ -136,36 +237,2 @@ if (node.loc) { | ||
/** | ||
* Certain node types do not get their endIndex reported from the jorje compiler, | ||
* or the number they report is not the end of the entire block, | ||
* so we'll have to figure it out by hand here. | ||
* This method mutates the node that was passed in, and assumes that `lastNodeLoc` | ||
* is set on it. | ||
* @param node the node to look at | ||
* @param sourceCode the entire source code | ||
* @param lineIndexes the indexes of the lines | ||
*/ | ||
function generateEndIndexForNode(node, sourceCode, lineIndexes) { | ||
switch (node["@class"]) { | ||
case apexNames.PROPERTY_MEMBER: | ||
case apexNames.SWITCH_STATEMENT: | ||
node.lastNodeLoc.endIndex = sourceCode.indexOf( | ||
"}", | ||
node.lastNodeLoc.endIndex, | ||
); | ||
node.lastNodeLoc.endLine = | ||
lineIndexes.findIndex(index => index > node.lastNodeLoc.endIndex) - 1; | ||
break; | ||
case apexNames.VARIABLE_DECLARATION_STATEMENT: | ||
node.lastNodeLoc.endIndex = sourceCode.indexOf( | ||
";", | ||
node.lastNodeLoc.endIndex, | ||
); | ||
node.lastNodeLoc.endLine = | ||
lineIndexes.findIndex(index => index > node.lastNodeLoc.endIndex) - 1; | ||
break; | ||
default: | ||
} | ||
return node; | ||
} | ||
/** | ||
* Generate metadata about empty lines for statement nodes. | ||
@@ -175,9 +242,3 @@ * This method is called recursively while visiting each node in the tree. | ||
* @param node the node being visited | ||
* @param sourceCode the entire source code | ||
* @param lineIndexes the indexes of the lines in the source code | ||
* @param emptyLineLocations a list of lines that are empty in the source code | ||
* @param emptyLineNodeMap a map of empty line to the node that is attached to | ||
* that line. Usually it is the statement right before it; however for certain | ||
* node type (e.g. IfElseBlock) that contains BlockStatement, it'll be the | ||
* outermost node (e.g. IfElseBlock instead of BlockStatement) | ||
* @param allowTrailingEmptyLine whether trailing empty line is allowed | ||
@@ -191,6 +252,3 @@ * for this node. This helps when dealing with statements that contain other | ||
node, | ||
sourceCode, | ||
lineIndexes, | ||
emptyLineLocations, | ||
emptyLineNodeMap, | ||
allowTrailingEmptyLine, | ||
@@ -200,6 +258,6 @@ ) { | ||
let allowTrailingEmptyLineWithin; | ||
const isSpecialClass = values.TRAILING_EMPTY_LINE_AFTER_LAST_NODE.includes( | ||
const isSpecialClass = constants.TRAILING_EMPTY_LINE_AFTER_LAST_NODE.includes( | ||
apexClass, | ||
); | ||
const trailingEmptyLineAllowed = values.ALLOW_TRAILING_EMPTY_LINE.includes( | ||
const trailingEmptyLineAllowed = constants.ALLOW_TRAILING_EMPTY_LINE.includes( | ||
apexClass, | ||
@@ -214,3 +272,2 @@ ); | ||
} | ||
let lastNodeLoc; | ||
Object.keys(node).forEach(key => { | ||
@@ -221,74 +278,63 @@ if (typeof node[key] === "object") { | ||
} | ||
const nodeLoc = generateExtraMetadata( | ||
generateExtraMetadata( | ||
node[key], | ||
sourceCode, | ||
lineIndexes, | ||
emptyLineLocations, | ||
emptyLineNodeMap, | ||
allowTrailingEmptyLineWithin, | ||
); | ||
if ( | ||
nodeLoc && | ||
(!lastNodeLoc || nodeLoc.endIndex > lastNodeLoc.endIndex) | ||
) { | ||
// This might not be the same node that `isLastNodeInArray` refers to, | ||
// since this searches for node in child objects instead of just child | ||
// arrays | ||
lastNodeLoc = nodeLoc; | ||
} else if (!nodeLoc && !lastNodeLoc) { | ||
lastNodeLoc = _getNodeLocation(node); | ||
} | ||
} | ||
}); | ||
if (isSpecialClass && lastNodeLoc) { | ||
// Store the last node information for some special node types, so that | ||
// we can add trailing empty lines after them. | ||
node.lastNodeLoc = lastNodeLoc; | ||
generateEndIndexForNode(node, sourceCode, lineIndexes); | ||
} | ||
const nodeLoc = _getNodeLocation(node); | ||
if ( | ||
apexClass && | ||
(nodeLoc || node.lastNodeLoc) && | ||
nodeLoc && | ||
allowTrailingEmptyLine && | ||
!node.isLastNodeInArray | ||
) { | ||
// There's a chance that multiple statements exist on 1 line, | ||
// so we only want to tag one of them as having a trailing empty line. | ||
// We do that by applying the trailing empty line only after the last node. | ||
// e.g. `if (a === 1) {} else {}\n\n`,the empty line should be applied | ||
// after the `else`, not the `if`. We keep track of which | ||
// nodes have trailingEmptyLine turned on for a certain line, then turn | ||
// it off for all but the last one. | ||
const nextLine = isSpecialClass | ||
? node.lastNodeLoc.endLine + 1 | ||
: nodeLoc.endLine + 1; | ||
const nextLine = nodeLoc.endLine + 1; | ||
const nextEmptyLine = emptyLineLocations.indexOf(nextLine); | ||
if (trailingEmptyLineAllowed && nextEmptyLine !== -1) { | ||
node.trailingEmptyLine = true; | ||
} | ||
} | ||
return nodeLoc; | ||
} | ||
if (emptyLineNodeMap[nextLine]) { | ||
const nodeMapEndIndex = emptyLineNodeMap[nextLine].lastNodeLoc | ||
? emptyLineNodeMap[nextLine].lastNodeLoc.endIndex | ||
: _getNodeLocation(emptyLineNodeMap[nextLine]).endIndex; | ||
const thisEndIndex = node.lastNodeLoc | ||
? node.lastNodeLoc.endIndex | ||
: nodeLoc.endIndex; | ||
if (nodeMapEndIndex > thisEndIndex) { | ||
node.trailingEmptyLine = false; | ||
/** | ||
* jorje sometimes gives us different ASTs depending on the white spaces | ||
* between keywords (#38), which makes it very difficult to know how many | ||
* nested expressions there are in a method call or a variable expression. | ||
* This method is used to calculate that number. | ||
* After this method is called on a node that contains a `dottedExpr`, | ||
* that node will contain a `numberOfDottedExpressions` property. | ||
* @param node the current node being visited. | ||
*/ | ||
function generateDottedExpressionMetadata(node) { | ||
Object.keys(node).forEach(key => { | ||
if (typeof node[key] === "object") { | ||
node[key] = generateDottedExpressionMetadata(node[key]); | ||
} | ||
if (key === "dottedExpr") { | ||
node.numberOfDottedExpressions = 1; | ||
if (node.names) { | ||
node.numberOfDottedExpressions = node.names.length; | ||
} | ||
if (node.dottedExpr && node.dottedExpr.value) { | ||
if (node.dottedExpr.value.numberOfDottedExpressions) { | ||
node.numberOfDottedExpressions += | ||
node.dottedExpr.value.numberOfDottedExpressions; | ||
} else if ( | ||
node.dottedExpr.value["@class"] === apexTypes.ARRAY_EXPRESSION && | ||
node.dottedExpr.value.expr.numberOfDottedExpressions | ||
) { | ||
node.numberOfDottedExpressions += | ||
node.dottedExpr.value.expr.numberOfDottedExpressions; | ||
} else { | ||
emptyLineNodeMap[nextLine].trailingEmptyLine = false; | ||
emptyLineNodeMap[nextLine] = node; | ||
node.numberOfDottedExpressions += 1; | ||
} | ||
} else { | ||
emptyLineNodeMap[nextLine] = node; | ||
} | ||
} | ||
} | ||
if (lastNodeLoc) { | ||
return lastNodeLoc; | ||
} | ||
return nodeLoc; | ||
}); | ||
return node; | ||
} | ||
@@ -302,3 +348,9 @@ | ||
const nodeLoc = _getNodeLocation(node); | ||
if (nodeLoc) { | ||
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 = | ||
lineIndexes.findIndex(index => index > nodeLoc.startIndex) - 1; | ||
} | ||
if (nodeLoc && !("endLine" in nodeLoc)) { | ||
nodeLoc.endLine = | ||
@@ -311,5 +363,10 @@ lineIndexes.findIndex(index => index > nodeLoc.endIndex) - 1; | ||
} | ||
// Edge case: Trigger Declaration Unit. | ||
// Somehow jorje thinks this node ends after the word `trigger`. | ||
} | ||
if (nodeLoc && !("column" in nodeLoc)) { | ||
nodeLoc.column = | ||
nodeLoc.startIndex - | ||
lineIndexes[ | ||
lineIndexes.findIndex(index => index > nodeLoc.startIndex) - 1 | ||
]; | ||
} | ||
Object.keys(node).forEach(key => { | ||
@@ -357,9 +414,12 @@ if (typeof node[key] === "object") { | ||
function parse(sourceCode, _, options) { | ||
sourceCode = sourceCode.trim(); | ||
const lineIndexes = getLineIndexes(sourceCode); | ||
let serializedAst; | ||
if (options.useStandaloneServer) { | ||
serializedAst = parseTextWithNailgun(sourceCode, options.serverPort); | ||
if (options.apexStandaloneParser === "built-in") { | ||
serializedAst = parseTextWithNailgun( | ||
sourceCode, | ||
options.apexStandalonePort, | ||
options.apexAnonymous, | ||
); | ||
} else { | ||
serializedAst = parseTextWithSpawn(sourceCode); | ||
serializedAst = parseTextWithSpawn(sourceCode, options.apexAnonymous); | ||
} | ||
@@ -370,6 +430,6 @@ let ast = {}; | ||
if ( | ||
ast[apexNames.PARSER_OUTPUT] && | ||
ast[apexNames.PARSER_OUTPUT].parseErrors.length > 0 | ||
ast[apexTypes.PARSER_OUTPUT] && | ||
ast[apexTypes.PARSER_OUTPUT].parseErrors.length > 0 | ||
) { | ||
const errors = ast[apexNames.PARSER_OUTPUT].parseErrors.map( | ||
const errors = ast[apexTypes.PARSER_OUTPUT].parseErrors.map( | ||
err => `${err.message}. ${err.detailMessage}`, | ||
@@ -379,13 +439,15 @@ ); | ||
} | ||
const commentNodes = ast[apexTypes.PARSER_OUTPUT].hiddenTokenMap | ||
.map(item => item[1]) | ||
.filter( | ||
node => | ||
node["@class"] === apexTypes.BLOCK_COMMENT || | ||
node["@class"] === apexTypes.INLINE_COMMENT, | ||
); | ||
ast = resolveAstReferences(ast, {}); | ||
fixNodeLocation(ast); | ||
handleNodeLocation(ast, sourceCode, commentNodes); | ||
ast = resolveLineIndexes(ast, lineIndexes); | ||
generateExtraMetadata( | ||
ast, | ||
sourceCode, | ||
lineIndexes, | ||
getEmptyLineLocations(sourceCode), | ||
{}, | ||
true, | ||
); | ||
ast = generateDottedExpressionMetadata(ast); | ||
generateExtraMetadata(ast, getEmptyLineLocations(sourceCode), true); | ||
attachComments(ast, sourceCode); | ||
@@ -392,0 +454,0 @@ } |
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
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
11853192
4070
149
1
2
8