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

Typescript conversion #32

Closed
wants to merge 13 commits into from

Conversation

rossknudsen
Copy link
Contributor

@rossknudsen rossknudsen commented Jan 25, 2020

This is a conversion to Typescript, removing Flow and updating build scripts.

Unit tests should be passing, but I need to manually test. Maybe we can get a pre-release, or is there another simple way to substitute an npm package in my extension?

@rossknudsen
Copy link
Contributor Author

Hmmm, looks like I need to fix the linting...

@rossknudsen
Copy link
Contributor Author

Ah looks like the version of node on the CI server is too low to run one of the new dependencies. Can we bump the version?

@connectdotz
Copy link
Collaborator

while I like typescript a lot personally, I am not the only contributor in this repo, @stephtr @seanpoulter, are you guys ok to move the repo to typescript?

@stephtr
Copy link
Member

stephtr commented Jan 26, 2020

while I like typescript a lot personally, I am not the only contributor in this repo, @stephtr @seanpoulter, are you guys ok to move the repo to typescript?

Personally, I definitely prefer working with TypeScript. It would also better fit to vscode-jest.

.eslintrc.js Outdated
extends: ['airbnb-base', 'plugin:flowtype/recommended', 'prettier', 'plugin:prettier/recommended'],
extends: [
'airbnb-typescript/base',
"airbnb/hooks",
Copy link
Member

Choose a reason for hiding this comment

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

I guess we should use quotes consistently 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My choice would have been for double quotes, but I tried to stick with the Prettier rules that were already present.

Copy link
Member

@stephtr stephtr Jan 26, 2020

Choose a reason for hiding this comment

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

I was just refering to some extends strings being single- and some being doublequoted. I would prefer enforcing consistent quotes for the whole project.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah my bad. Was reading that comment on mobile. Not sure why I've got a mixture there. Will fix.

@@ -1,4 +1,4 @@
// @flow

Copy link
Member

Choose a reason for hiding this comment

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

Why do some files start with an empty line?

Copy link
Contributor Author

@rossknudsen rossknudsen Jan 26, 2020

Choose a reason for hiding this comment

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

Thats a good question. It took me ages to figure out why some unit tests were failing. Turns out the line numbers are hard-coded in them and I was trying to change the minimal amount of code, so that this PR was readable.

So I didn't update the tests with new line numbers.

const fixtures = __dirname;

function parserTests(parse: (file: string) => ParserResult) {
function parserTests(parse: (file: string, data?: string | undefined) => ParseResult) {
Copy link
Member

Choose a reason for hiding this comment

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

The ? already contains undefined.

Copy link
Member

@stephtr stephtr left a comment

Choose a reason for hiding this comment

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

I didn't have time to go through all changes, but these are my comments for some of them.

Comment on lines 19 to 21
name: string | null = null,
) => assertBlock(block, {column: sc, line: sl}, {column: ec, line: el}, name);
const assertBlock = (block, start, end, name: ?string = null) => {
const assertBlock = (block, start, end, name: string | null = null) => {
Copy link
Member

Choose a reason for hiding this comment

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

We could change this to just name?: string.


// check test blocks, including the template literal
const found = descBlock.children.filter(
const found = descBlock?.children?.map(b => b as ItBlock)?.filter(
Copy link
Member

Choose a reason for hiding this comment

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

In my opinion we don't need a ?. after map.

Copy link
Member

Choose a reason for hiding this comment

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

👍

How did you add these @rossknudsen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@seanpoulter if you mean this specific piece of code, it now looks like this:

// check test blocks, including the template literal
const found = descBlock.children
.map(b => b as NamedBlock)
.filter(
    b =>
    b.name === 'needs to have a PR number' ||
    b.name === 'does not validate without josh' ||
    b.name === 'does not validate when ${key} is missing'
);
expect(found.length).toBe(3);

I was just trying to assert that the type of the array elements were NamedBlocks (originally ItBlocks). There is potentially another way to write this which is arguably more elegant. Now that the children property is not potentially null, it is a lot clearer.

If you are talking more generally, you might need to give me some more context.

Comment on lines 419 to 421
export {
parserTests,
};
}
Copy link
Member

Choose a reason for hiding this comment

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

Why not use export function parserTests?

@@ -184,24 +186,24 @@ function parserTests(parse: (file: string) => ParserResult) {
it('finds test blocks within describe blocks', () => {
const data = parse(`${fixtures}/dangerjs/travis-ci.example`);
const descBlock = data.describeBlocks[1];
expect(descBlock.children.length).toBe(4);
expect(descBlock.children?.length).toBe(4);
Copy link
Member

Choose a reason for hiding this comment

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

Can children really be not an (empty) array? I'm just wondering about the change between old and new code.

src/Runner.ts Outdated
import ProjectWorkspace from './project_workspace';
import {createProcess} from './Process';

// This class represents the running process, and
// passes out events when it understands what data is being
// pass sent out of the process
export default class Runner extends EventEmitter {
debugprocess: ChildProcess;
debugprocess: ChildProcess | null = null;
Copy link
Member

Choose a reason for hiding this comment

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

What about debugprocess?: ChildProcess;?

@@ -30,24 +29,24 @@ describe('createProcess', () => {
const args = [];
createProcess(workspace, args);

expect(spawn.mock.calls[0][0]).toBe('jest');
expect(spawn.mock.calls[0][1]).toEqual([]);
expect((spawn as any).mock.calls[0][0]).toBe('jest');
Copy link
Member

Choose a reason for hiding this comment

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

One could also use the as Mock<any> type from Jest.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, slightly more type-safe. Any ideas on replacing the any part of the type with something more specific?


export class ParsedNode {
type: ParsedNodeType;

start: Location;
start: Location | undefined = undefined;
Copy link
Member

Choose a reason for hiding this comment

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

On such occasions start?: Location; would be sufficient.

@@ -1,14 +1,15 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable max-classes-per-file */
Copy link
Member

Choose a reason for hiding this comment

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

In my opinion we could also get rid of the max-classes-per-file policy project-wide.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

What is your opinion of placement of the copyright notice vs linting configuration? I had just left it where the intellisense put it, at the very top.

Comment on lines +30 to 40
export const ParsedNodeTypes: {
describe: 'describe';
expect: 'expect';
it: 'it';
root: 'root';
} = {
describe: 'describe',
expect: 'expect',
it: 'it',
root: 'root',
};
Copy link
Member

Choose a reason for hiding this comment

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

It has to be tested, but this could be replaced by either an enum or export type ParsedNodeTypes = 'describe' | 'expect' | ....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I was tempted to rewrite some of the parser code relying on TS more, but I see that #30 is already rewriting the parser code. It would probably make more sense to merge those changes 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.

Actually having read that PR, I'm inclined to wait until it is merged because it will remove the typescript parser. That reduces the delta in this PR.


file: string;

children: ?Array<ParsedNode>;
children?: Array<ParsedNode>;
Copy link
Member

Choose a reason for hiding this comment

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

ParsedNode[] is easier to read than Array<...>.

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 agree. Pretty sure this is a Prettier/ESLint rule.

@rossknudsen
Copy link
Contributor Author

Huh, somehow the coverage dropped...

@rossknudsen
Copy link
Contributor Author

while I like typescript a lot personally, I am not the only contributor in this repo, @stephtr @seanpoulter, are you guys ok to move the repo to typescript?

Fair enough, I was going by this comment which is probably old/stale now. I ran across the comment because I wanted to access more of the Jest settings. I guess I went down a bit of a rabbit hole with this one...

@seanpoulter
Copy link
Member

seanpoulter commented Jan 27, 2020

Thanks for the huge effort @rossknudsen! 👏

--

@rossknudsen wrote

Unit tests should be passing, but I need to manually test. Maybe we can get a pre-release, or is there another simple way to substitute an npm package in my extension?

Yes, you can substitute npm packages using "linking". Here's the docs for npm and yarn that are probably more clear than what I'd write. ;)

--

@connectdotz wrote:

while I like typescript a lot personally, I am not the only contributor in this repo, @stephtr @seanpoulter, are you guys ok to move the repo to typescript?

I've got two answers for this: one with best interest of the project in mind, and the other from my own perspective.

  1. With the best interest in the project in mind, I'd say yes, switching to TypeScript makes sense because folks are more familiar with TypeScript than Flow. I don't think any of the other reasons I've considered sway me one way or the other.
    Pros
    • More type information will help folks use the library in lieu of documentation
    • We can leverage more of the types exported from the facebook/jest repo
    • TypeScript has more adoption than Flow [1] [2]
    • TypeScript may have better IDE integration than Flow
    • TypeScript might improve our code quality
    Cons
    • TypeScript is still a barrier to entry over "regular" "JavaScript" -- not everyone will keep up with the ES20xx language features
    • Spending time on test case coverage and code review may be a better time investment [3]
  2. With my own interest in mind, I see this is a PR that's too big for me to approach so I'm biased towards keeping the status quo. While I think this is adding a lot of value, we may have missed some of the conversations we need like:
    • Do the maintainers have the capacity to review the PR? (I don't)
    • Can we make the switch from Flow to TypeScript in incremental, easy to review PRs?
    • Do we need to discussion what TS config we'll use, and is it familiar? (As an arbitrary example, this is the first time I've seen optional chaining in a repo and sometimes they feel icky, like in tests).
    • What test cases do we need to have confidence in this, especially if it's too big to review?
    • (edited to add this): Are we OK to migrate AND refactor at the same time? For example, can we convert the Flow Maybe to the TypeScript optional?

[1]: https://2019.stateofjs.com/javascript-flavors/typescript/
[2]: https://2019.stateofjs.com/javascript-flavors/other-tools/
[3]: https://medium.com/javascript-scene/the-typescript-tax-132ff4cb175b

.eslintrc.js Outdated Show resolved Hide resolved
let line = null;
let short: string | undefined;
let terse: string | undefined;
let line: number | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

The change from null to undefined stands out to me as a potentially risky API change. The risk is that a caller uses all three types from the Flow Maybe type (type | null | void (undefined)) to differentiate between values that aren't in the map/object, are invalid, or are a valid number. It makes me worry that I'd/we'd have to do a more thorough code review or testing.

In this particular case, the caller seems fine but there's still the rough edge of how we're using this.lineOfError(...) || undefined to do ... something. What would ever be falsy enough to end up as undefined?! I actually had to run this in the Dev Tools console just to check the falsy precedence:

image

I guess I learned (or relearned) that. ☺️ Since I'll probably forget that the next time I look at it, I'd probably suggest going for:

-         line = this.lineOfError(message, filename) || undefined;
+         line = this.lineOfError(message, filename);

with lineOfError returning undefined. I'm not a fan of the magic numbers like [1] in there so I'd hope to see something like:

-   lineOfError(message: string, filePath: string): number | null {
-     const filename = path.basename(filePath);
-     const restOfTrace = message.split(filename, 2)[1];
-     return restOfTrace ? parseInt(restOfTrace.split(':')[1], 10) : null;
-   }
+   lineOfError(message: string, filepath: string): ?number {
+     const filename = path.basename(filepath);
+     const [_msgIncludingFilename, msgAfterFilename] = message.split(filename, 2);
+     if (! msgAfterFilename) return;
+     const [_colon, lineNumber] = msgAfterFilename(':');
+     return parseInt(lineNumber, 10);
+   }

--

The underlying question for the folks reviewing is are we OK with migrating and refactoring at the same time. Here's the docs for the Flow Maybe type.

@rossknudsen
Copy link
Contributor Author

@seanpoulter I appreciate the time taken to write your thoughts regarding this PR. I reviewed the changed files again and it is substantial. My original intent was to avoid a massive delta as it has potential to introduce regressions etc and part of the reason why people wouldn't want to convert in the first place. Also 100% appreciate your limited time in reviewing this.

In light of all this, I stepped back and thought about whether its possible to take an incremental approach to conversion. So I looked at the recommendations for conversion from Microsoft and cherry-picked some changes from this branch into a new branch. If you like this approach I can open another PR for review. I think I've kept everything operational while converting just one file to Typescript.

@connectdotz
Copy link
Collaborator

In light of all this, I stepped back and thought about whether its possible to take an incremental approach to conversion... If you like this approach I can open another PR for review. I think I've kept everything operational while converting just one file to Typescript.

I like that! 👍

@connectdotz
Copy link
Collaborator

superseded by #33

@connectdotz connectdotz closed this Feb 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants