Skip to content

Commit

Permalink
Support alignment in doc comments
Browse files Browse the repository at this point in the history
see #134
  • Loading branch information
jcberquist committed Dec 7, 2021
1 parent 095e9b9 commit d26d449
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 0 deletions.
1 change: 1 addition & 0 deletions .cfformat.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"alignment.consecutive.assignments": false,
"alignment.consecutive.params": false,
"alignment.consecutive.properties": false,
"alignment.doc_comments": false,
"array.empty_padding": false,
"array.multiline.comma_dangle": false,
"array.multiline.element_count": 4,
Expand Down
4 changes: 4 additions & 0 deletions data/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"true":"// alignment.consecutive.properties: true\nproperty name=\"requestService\" inject=\"coldbox:requestService\";\nproperty name=\"log\" inject=\"logbox:logger:{this}\";",
"false":"// alignment.consecutive.properties: false\nproperty name=\"requestService\" inject=\"coldbox:requestService\";\nproperty name=\"log\" inject=\"logbox:logger:{this}\";"
},
"alignment.doc_comments":{
"true":"// alignment.doc_comments: true\n/**\n * @name test\n * @b another param\n */",
"false":"// alignment.doc_comments: false\n/**\n * @name test\n * @b another param\n */"
},
"array.empty_padding":{
"true":"// array.empty_padding: true\nmyArray = [ ];",
"false":"// array.empty_padding: false\nmyArray = [];"
Expand Down
7 changes: 7 additions & 0 deletions data/reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
},
"type": "boolean"
},
"alignment.doc_comments": {
"description": "When true, cfformat will attempt to align the @param descriptions and @throws descriptions in doc comments.",
"example": {
"code": "/**\n * @name test\n * @b another param\n */"
},
"type": "boolean"
},
"array.empty_padding": {
"description": "When true, empty arrays are padded with a space.",
"example": {
Expand Down
104 changes: 104 additions & 0 deletions models/Alignment.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ component accessors="true" {
'(?:"[^"]*"|''[^'']*''|#identifier#)', // attribute value
')?' // attribute value is optional
];
variables.docParamRegex = [
'^([ \t]*\*\s*)', // leading indentation and *
'(?!(?i:@throws|@return))', // not @throws or @return
'(@#identifier#)', // param name
'([^\r\n]*)\r?\n' // param description (rest of the line)
];
variables.docThrowsRegex = [
'^([ \t]*\*\s*)', // leading indentation and *
'(?i:(@throws\s+#identifier#))',
'([^\r\n]*)\r?\n' // throws description (rest of the line)
];

function init() {
var patternClass = createObject('java', 'java.util.regex.Pattern');
Expand All @@ -45,6 +56,9 @@ component accessors="true" {
variables.propertiesPattern = patternClass.compile(propertiesRegex.toList(''), 8);
variables.paramsPattern = patternClass.compile(propertiesRegex.toList('').replace('property', 'param'), 8);
variables.attributePattern = patternClass.compile(attributeRegex.toList(''), 8);
variables.docParamPattern = patternClass.compile(docParamRegex.toList(''), 8);
variables.docThrowsPattern = patternClass.compile(docThrowsRegex.toList(''), 8);

return this;
}

Expand Down Expand Up @@ -151,6 +165,59 @@ component accessors="true" {
return src;
}

string function alignDocComments(required string src) {
var replacements = [];
var ranges = stringRanges.walk(src);

for (var matcher in [docParamPattern.matcher(src), docThrowsPattern.matcher(src)]) {
var index = 0;
var strRanges = {index: 1, ranges: ranges};

while (matcher.find(index)) {
index = matcher.end();

if (!inDocRange(matcher.start(2), strRanges)) {
continue;
}

var group = [matcher.toMatchResult()];
var indent = matcher.group(1);

while (true) {
matcher.region(index, len(src));

if (matcher.lookingAt()) {
if (
inDocRange(matcher.start(2), strRanges) &&
len(indent) == len(matcher.group(1))
) {
group.append(matcher.toMatchResult());
index = matcher.end();
continue;
}
}

if (arrayLen(group) > 1) {
replacements.append(parseDocParamGroup(group), true);
}
break;
}
}
}

replacements.sort(function(a, b) {
if (a.start > b.start) return 1;
if (a.start < b.start) return -1;
return 0;
});

for (var replacement in replacements.reverse()) {
src = src.substring(0, replacement.start) & replacement.line & src.substring(replacement.end);
}

return src;
}

private function parseAssignmentGroup(group) {
var longestKey = getLongestAssignmentKey(group);
var output = [];
Expand Down Expand Up @@ -214,6 +281,26 @@ component accessors="true" {
return longestValues;
}

private function parseDocParamGroup(group) {
var longestName = getLongestDocParamName(group);
var output = [];
for (var match in group) {
var line = match.group(2);
line &= repeatString(' ', longestName - line.len());
line &= ' ' & match.group(3).ltrim();
output.append({start: match.start(2), end: match.end(3), line: line.trim()});
}
return output;
}

private function getLongestDocParamName(group) {
var longest = 0;
for (var m in group) {
longest = max(longest, m.end(2) - m.start(2));
}
return longest;
}

private function inStringRange(idx, strRanges) {
while (
strRanges.ranges.len() >= strRanges.index &&
Expand All @@ -227,4 +314,21 @@ component accessors="true" {
);
}

private function inDocRange(idx, strRanges) {
while (
strRanges.ranges.len() >= strRanges.index &&
(
strRanges.ranges[strRanges.index].end - 1 < idx ||
strRanges.ranges[strRanges.index].name != 'doc_comment'
)
) {
strRanges.index++;
}
return (
strRanges.ranges.len() >= strRanges.index &&
strRanges.ranges[strRanges.index].start <= idx &&
strRanges.ranges[strRanges.index].name == 'doc_comment'
);
}

}
3 changes: 3 additions & 0 deletions models/CFFormat.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ component accessors="true" {
if (settings['alignment.consecutive.params']) {
formatted = this.alignment.alignAttributes(formatted, 'params');
}
if (settings['alignment.doc_comments']) {
formatted = this.alignment.alignDocComments(formatted);
}

return bom & formatted;
}
Expand Down
3 changes: 3 additions & 0 deletions models/StringRanges.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ component {

variables.CFSCRIPT = [
'line_comment',
'doc_comment',
'multiline_comment',
'string_single',
'string_double',
Expand Down Expand Up @@ -52,6 +53,7 @@ component {
escaped_single_quote: ['''''', '(?=.)', [], 'first'],
hash: ['##', '##', CFSCRIPT, 'first'],
line_comment: ['//', '\n', [], 'first'],
doc_comment: ['/\*\*', '\*/', [], 'first'],
multiline_comment: ['/\*', '\*/', [], 'first'],
string_double: [
'"',
Expand Down Expand Up @@ -110,6 +112,7 @@ component {
'string_single',
'string_double',
'line_comment',
'doc_comment',
'multiline_comment',
'tag_comment',
'cfquery_tag'
Expand Down
22 changes: 22 additions & 0 deletions reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,28 @@ property name="requestService" inject="coldbox:requestService";
property name="log" inject="logbox:logger:{this}";
```

## alignment.doc_comments

Type: _boolean_

Default: **false**

When true, cfformat will attempt to align the @param descriptions and @throws descriptions in doc comments.

```cfc
// alignment.doc_comments: true
/**
* @name test
* @b another param
*/
// alignment.doc_comments: false
/**
* @name test
* @b another param
*/
```

## array.empty_padding

Type: _boolean_
Expand Down
26 changes: 26 additions & 0 deletions tests/data/alignDocComments/formatted.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
component {

/**
* Try's to get a jwt token from the authorization header or the custom header
* defined in the configuration or passed in by you. If it is a valid token and it decodes we will then
* continue to validate the subject it represents. Once those are satisfied, then it will
* store it in the `prc` as `prc.jwt_token` and the payload as `prc.jwt_payload`.
*
* @token The token to parse and validate, if not passed we call the discoverToken() method for you.
* @storeInContext By default, the token will be stored in the request context
* @authenticate By default, the token will be authenticated, you can disable it and do manual authentication.
*
* @throws TokenExpiredException If the token has expired or no longer in the storage (invalidated)
* @throws TokenInvalidException If the token doesn't verify decoding
* @throws TokenNotFoundException If the token cannot be found in the headers
*
* @returns The payload for convenience
*/
struct function parseToken(
string token = discoverToken(),
boolean storeInContext = true,
boolean authenticate = true
) {
}

}
5 changes: 5 additions & 0 deletions tests/data/alignDocComments/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{
"alignment.doc_comments": true
}
]
27 changes: 27 additions & 0 deletions tests/data/alignDocComments/source.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
component {

/**
* Try's to get a jwt token from the authorization header or the custom header
* defined in the configuration or passed in by you. If it is a valid token and it decodes we will then
* continue to validate the subject it represents. Once those are satisfied, then it will
* store it in the `prc` as `prc.jwt_token` and the payload as `prc.jwt_payload`.
*
* @token The token to parse and validate, if not passed we call the discoverToken() method for you.
* @storeInContext By default, the token will be stored in the request context
* @authenticate By default, the token will be authenticated, you can disable it and do manual authentication.
*
* @throws TokenExpiredException If the token has expired or no longer in the storage (invalidated)
* @throws TokenInvalidException If the token doesn't verify decoding
* @throws TokenNotFoundException If the token cannot be found in the headers
*
* @returns The payload for convenience
*/
struct function parseToken(
string token = discoverToken(),
boolean storeInContext = true,
boolean authenticate = true
) {
}

}
3 changes: 3 additions & 0 deletions tests/specs/alignmentSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ component extends=tests.FormatBaseSpec {
it('aligns param attributes', function() {
runTests(loadData('alignParamAttributes'));
});
it('aligns doc comment param and throws descriptions', function() {
runTests(loadData('alignDocComments'));
});
});
}

Expand Down

0 comments on commit d26d449

Please sign in to comment.