Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: print clang-format output #186

Merged
merged 26 commits into from Jul 17, 2018
Merged

Conversation

lee-tammy
Copy link
Contributor

@lee-tammy lee-tammy commented Jul 10, 2018

Still working on the feature so that it can support non-utf8 characters.

Fixes #181

@googlebot
Copy link

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. I signed it!) and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

@kjin kjin self-requested a review July 10, 2018 01:00
@lee-tammy lee-tammy changed the title Print clang-format output feature feat: print clang-format output Jul 10, 2018
@googlebot
Copy link

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.
In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again. If the bot doesn't comment, it means it doesn't think anything has changed.

@lee-tammy
Copy link
Contributor Author

Should work on files with non-utf8 characters now

@googlebot
Copy link

CLAs look good, thanks!

Copy link
Contributor

@kjin kjin left a 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",
Copy link
Contributor

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.

Copy link
Contributor Author

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';
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

@lee-tammy lee-tammy Jul 10, 2018

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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')
}

Copy link
Contributor Author

@lee-tammy lee-tammy Jul 11, 2018

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.

Copy link
Contributor

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.

Copy link
Contributor Author

@lee-tammy lee-tammy Jul 12, 2018

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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')));
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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');
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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);
}
Copy link
Contributor

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)

Copy link
Contributor Author

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",
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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).

Copy link
Contributor

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.

Copy link
Contributor

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

@ofrobots
Copy link
Contributor

I just tried this out. This looks really cool!

image

src/format.ts Outdated
import { start } from 'repl';
import {readFilep} from './util';

interface replacement {
Copy link
Contributor

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)

Copy link
Contributor Author

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(
Copy link
Contributor

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.

Copy link
Contributor Author

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++) {
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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});
Copy link
Contributor

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-io
Copy link

codecov-io commented Jul 12, 2018

Codecov Report

Merging #186 into master will increase coverage by 0.21%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
test/test-format.ts 100% <100%> (ø) ⬆️
src/format.ts 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e107ab3...2b80caa. Read the comment docs.

@kjin
Copy link
Contributor

kjin commented Jul 13, 2018

cc/ @jplaisted

Copy link
Contributor

@kjin kjin left a 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):
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Missing ending >

Copy link
Contributor Author

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) {
Copy link
Contributor

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"

Copy link
Contributor Author

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

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\" ];';
Copy link
Contributor

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.

Copy link
Contributor Author

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'
Copy link
Contributor

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?

kjin
kjin previously approved these changes Jul 16, 2018
Copy link
Contributor

@kjin kjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@kjin kjin dismissed their stale review July 16, 2018 17:20

Tests don't pass

Copy link
Contributor

@kjin kjin left a 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!

Copy link
Contributor

@kjin kjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@kjin kjin merged commit f7c75ee into google:master Jul 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants