-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
feat: print clang-format output #186
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here (e.g. What to do if you already signed the CLAIndividual signers
Corporate signers
|
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
Should work on files with non-utf8 characters now |
5a29ea8
to
22933d6
Compare
CLAs look good, thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Algorithm generally looks good! Left some comments.
package.json
Outdated
"inquirer": "^6.0.0", | ||
"meow": "^5.0.0", | ||
"pify": "^3.0.0", | ||
"rimraf": "^2.6.2", | ||
"tslint": "^5.9.1", | ||
"update-notifier": "^2.5.0", | ||
"write-file-atomic": "^2.3.0" | ||
"utfstring": "^2.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think utfstring
and xml2js
are no longer used?
After you remove them, you can run npm install
again to effect changes in package-lock.json
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
@@ -15,11 +15,18 @@ | |||
*/ | |||
import * as fs from 'fs'; | |||
import * as path from 'path'; | |||
import {promisify} from 'util'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe promisify
isn't available in Node 6, which is the minimum version we support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
@@ -38,7 +45,7 @@ export async function format( | |||
fix = false; | |||
} | |||
|
|||
// If the project has a .clang-format – use it. Else use the default as an | |||
// If the project has a .clang-format i– use it. Else use the default as an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this an accidental change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I changed it back
src/format.ts
Outdated
|
||
if (formatErr.length === null || formatErr.offset === null || | ||
formatErr.fix === null) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest printing a message here on stderr to suggest that clang-format didn't emit expected output before returning. Also, maybe it would make more sense to continue
here rather than returning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, instead of checking if the variables are null, should I check if they are not null and using continue ...and have an else block with the print statement that tells the user that there are no formatting issues? The only case that I can think of where formatErr.length, offset, and fix would be null, is if there are no formatting issues.
if (formatErr.length !== null && formatErr.offset !== null &&
formatErr.fix !== null) {
continue;
}else{
options.logger.log('clang-format reported no errors')
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take a look at the changes I made so far. I'm returning an empty string[] instead of undefined. I don't think it's necessary to print to stderr because it is not an error when formatErr.length/offset/fix are null. it means that the format of the file is fine and does not need to be changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only case that I can think of where formatErr.length, offset, and fix would be null, is if there are no formatting issues.
Yep, I forgot that it's possible for there to not be replacements at all, indicating no formatting issues. For handling this, you can consider continue
-ing earlier, even before the regexes are being executed, by checking whether the replacement
tag occurs at all. That way, when you get to the point where formatErr.length === null || formatErr.offset === null || formatErr.fix === null
is being evaluated, you know that if that statement is true, it indicates that clang-format
didn't emit expected output. Even though I wouldn't expect clang-format
to change its output xml format in the future, this would help us disambiguate whether we are continuing because there are no formatting issues, or because an unexpected change occurred in clang-format
's output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I added an if statement with continue before calling the getReplacement method that uses regex. Also, I made it so that it throws an error if the regex results for offset, length, and fixes arrays are null or if their lengths are not the same.
src/format.ts
Outdated
formatErr.fix[j] = entities.decodeXML(formatErr.fix[j]); | ||
} | ||
|
||
const read = promisify(fs.readFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken, I think the promisified readFile
is already available in utils.ts
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
replaced.push(replacements[errOffset.length - 1]); | ||
replaced.push(substring( | ||
data, +errOffset[errOffset.length - 1] + +errLength[errOffset.length - 1], | ||
Buffer.byteLength(data, 'utf8'))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/format.ts
Outdated
* @param diffs contains all information about the formatting changes | ||
* @param options | ||
*/ | ||
function printDiffs(file: string, diffs: any, options: Options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that if you import
from diff
, then you can get the interface that matches the any
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
import {Options} from './cli'; | ||
import {createProgram} from './lint'; | ||
|
||
// Exported for testing purposes. | ||
export const clangFormat = require('clang-format'); | ||
const xml2js = require('xml2js'); | ||
const chalk = require('chalk'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use import * as x from 'x'
instead of const x = require('x')
? TS will prompt you for type definitions -- you can get them with
npm install --save-dev @types/x
The @types/
packages contain type definitions for each package. Excluding xml and utf (because you're not using them), I believe the rest of them all have this associated package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
/** | ||
* Substring by bytes | ||
* | ||
* @param str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you fill in the parameter decsriptions here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return Buffer.from(str, encoding) | ||
.slice(indexStart, indexEnd) | ||
.toString(encoding); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a new line to the end of this file? (It's our general practice to do so)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
package.json
Outdated
@@ -42,6 +42,7 @@ | |||
"dependencies": { | |||
"chalk": "^2.4.1", | |||
"clang-format": "1.2.3", | |||
"entities": "^1.1.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you miss diff
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, added it back
src/format.ts
Outdated
file, 'no new file', text, fixed, 'no old header', 'no new header', | ||
{context: 3}); | ||
jsdiff.applyPatch('diff', diff); | ||
return Promise.resolve(diff); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this to simply return diff
. Async functions automatically wrap return values in a promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
*/ | ||
function substring( | ||
str: string, indexStart: number, indexEnd: number, encoding = 'utf8') { | ||
return Buffer.from(str, encoding) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks odd. Since we already had a string – we already had decoded it using whatever encoding that was necessary. Why are we converting to a buffer and then converting back to a string? This does extra work in encoding/decoding, and will be buggy in cases where a different encoding ought to have been used (as rare as it might be).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't an available bytesSubstring
function out there. I was thinking that we should publish this as separate module (it does in fact have subtle differences from substring
).
Given that this function is only used for showing output, and that JS files are generally written with UTF-8 encoding, I feel like it's acceptable to say (for now) that if your file is encoded differently, you're on your own. That being said, any static tool that prints code snippets is going to have to do the same operation somewhere, so maybe we should look to tslint
or others for inspiration. I wouldn't be too surprised if tslint
makes the UTF-8 assumption as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discovered this: palantir/tslint#2810
src/format.ts
Outdated
import { start } from 'repl'; | ||
import {readFilep} from './util'; | ||
|
||
interface replacement { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interfaces names should be capitalized. Also, could you add a JSDoc describing the role of this object? (By JSDoc I mean /** */
style rather than //
style)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
@@ -100,11 +101,11 @@ function fixFormat(srcFiles: string[], baseArgs: string[]): Promise<boolean> { | |||
* | |||
* @param srcFiles list of source files | |||
*/ | |||
function checkFormat(srcFiles: string[], baseArgs: string[], options: Options): Promise<boolean> { | |||
async function checkFormat( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this function does not need to be async
, since the use of await
is in a nested function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, forgot to remove
src/format.ts
Outdated
|
||
out.on('end', async () => { | ||
const files = output.split('<?xml version=\'1.0\'?>\n'); | ||
for (let i = 1; i < files.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend using slice
so that we can just start this loop at i = 0
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
for (let i = 1; i < files.length; i++) { | ||
const replacements = getReplacements(files[i]); | ||
if (replacements.length > 0) { | ||
const diff = await getDiffObj(srcFiles[i - 1], replacements) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line seems to be missing a ;
? Maybe you need to run npm run fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
src/format.ts
Outdated
for(let i = 1; i < files.length; i++){ | ||
function getReplacements(output: string): replacement[] { | ||
const replacements: replacement[] = []; | ||
const offsets: string[]|null = output.match(/(?<=offset=\')(\d+)(?=\')/g); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the approach in this function would benefit from an inline comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. Check to see if it's ok
src/format.ts
Outdated
const fixed = | ||
performFixes(text, formatErr.offset, formatErr.length, formatErr.fix); | ||
const diff = jsdiff.structuredPatch( | ||
'oldFile', 'newFile', text, fixed, 'old', 'new', {context: 3}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can probably just assign empty strings, if you aren't reading them elsewhere.
Codecov Report
@@ Coverage Diff @@
## master #186 +/- ##
==========================================
+ Coverage 98.15% 98.37% +0.21%
==========================================
Files 12 12
Lines 652 737 +85
Branches 55 61 +6
==========================================
+ Hits 640 725 +85
Misses 12 12
Continue to review full report at Codecov.
|
cc/ @jplaisted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there!
src/format.ts
Outdated
@@ -92,7 +107,8 @@ function fixFormat(srcFiles: string[], baseArgs: string[]): Promise<boolean> { | |||
* | |||
* @param srcFiles list of source files | |||
*/ | |||
function checkFormat(srcFiles: string[], baseArgs: string[]): Promise<boolean> { | |||
function checkFormat(srcFiles: string[], baseArgs: string[], options: Options): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Can we emulate format
and put the options
argument first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
|
||
let xmlLines = fileXML.split('\n'); | ||
// The first and last two elements in xmlLines are not needed | ||
xmlLines = xmlLines.slice(1, xmlLines.length - 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: It wasn't immediately obvious to me that xmlLines.length - 2
is correct. If you define xmlLines
as fileXML.trim().split('\n')
then it will guarantee one element per non-empty line, so you could probably use xmlLines.length - 1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
|
||
for (let i = 0; i < xmlLines.length; i++) { | ||
// Uses regex to capture the xml attribute and element | ||
// XML format: <replacement offset='OFFSET' length='LENGTH'>FIX</replacement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Missing ending >
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
* @param diffs contains all information about the formatting changes | ||
* @param options | ||
*/ | ||
function printDiffs(diffs: jsdiff.IUniDiff, options: Options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: For string constructions in this file, try string interpolation.
const x = 2;
console.log(`${x + x} = 4`) // prints "4 = 4"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using string interpolation now. Check if it's okay
test/test-format.ts
Outdated
import * as format from '../src/format'; | ||
import {nop} from '../src/util'; | ||
|
||
import {withFixtures} from './fixtures'; | ||
|
||
// clang-format won't pass this code because of trailing spaces. | ||
const BAD_CODE = 'export const foo = [ 2 ];'; | ||
const GOOD_CODE = 'export const foo = [2];'; | ||
const BAD_CODE = '//🦄\nexport const foo = [ \"2\" ];'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed in person, let's have the unicorn be in its own test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
src/format.ts
Outdated
|
||
for (let i = 0; i < xmlLines.length; i++) { | ||
// Uses regex to capture the xml attribute and element | ||
// XML format: <replacement offset='OFFSET' length='LENGTH'>FIX</replacement | ||
// XML format: <replacement offset='OFFSET' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it was "fixed" with npm run fix. Can you put the <...>
on its own line so it doesn't get broken up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, and actually tests do pass!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Still working on the feature so that it can support non-utf8 characters.
Fixes #181