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#Provide a quick-fix for non-exported imports #72

Open
wants to merge 12 commits into
base: oss-hackathon-37440
Choose a base branch
from

Conversation

Richard-Lynch
Copy link

@Richard-Lynch Richard-Lynch commented Jun 16, 2022

Fixes microsoft#37440

If:

  • The import is from a .d.ts file, do nothing.

  • The declaration is a single variable (e.g. let a = 1): update to export let a = 1

  • The declaration is a single function (e.g. function a(){...}): update to export function a(){...}

  • The declaration is in a list and contains multiple variables (e.g. let a = 1, b = 2)

    • No export {...} found in the file: create an export {...} and add to that
    • Multiple export {...} found in the file: add to the last export {...}

Testing performed
Local testing + tests added

Additional context
Based on this closed PR

I think I addressed all the comments except this, which seems reasonable to me!

@Richard-Lynch Richard-Lynch changed the title Feat#Quickfix non exported imports Feat#Provide a quick-fix for non-exported imports Jun 16, 2022
@Richard-Lynch
Copy link
Author

Maybe worth adding (at)DanielRosenwasser and (at)sandersn when this is added to the TS repo

@Richard-Lynch Richard-Lynch marked this pull request as ready for review June 16, 2022 14:49
Copy link

@mkubilayk mkubilayk left a comment

Choose a reason for hiding this comment

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

Looking very good - some minor comments.

const node = localSymbol.valueDeclaration.parent;

return changes.insertExportModifier(sourceFile, node);
}

Choose a reason for hiding this comment

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

Do we need special handling for class declarations as well?

Copy link
Author

Choose a reason for hiding this comment

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

class is captured/handled with isDeclarationStatement but I've added some tests and simplified that logic

// If there is an existing export
if (
namedExportDeclaration?.exportClause &&
isNamedExports(namedExportDeclaration.exportClause)

Choose a reason for hiding this comment

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

Should we check if this is a type-only export? My understanding is that this would change:

export type { existingThing };

to

export { existingThing, newThing };

which may not be desired.

Copy link
Author

Choose a reason for hiding this comment

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

nice! yes, this is a good test case, adding now

if (
isExportDeclaration(statement) &&
statement.exportClause &&
isNamedExports(statement.exportClause)

Choose a reason for hiding this comment

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

We should skip re-export statements here: export { a } from "./something";.

Copy link
Author

Choose a reason for hiding this comment

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

yup nice, added

Comment on lines 188 to 206
factory.updateExportDeclaration(
/*node*/ namedExportDeclaration,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
/*exportClause*/ factory.updateNamedExports(
/*node*/ namedExportDeclaration.exportClause,
/*elements*/ sortSpecifiers(
namedExportDeclaration.exportClause.elements.concat(
factory.createExportSpecifier(
/*isTypeOnly*/ false,
/*propertyName*/ undefined,
node
)
)
)
),
/*moduleSpecifier*/ undefined,
/*assertClause*/ undefined
)

Choose a reason for hiding this comment

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

Suggested change
factory.updateExportDeclaration(
/*node*/ namedExportDeclaration,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
/*exportClause*/ factory.updateNamedExports(
/*node*/ namedExportDeclaration.exportClause,
/*elements*/ sortSpecifiers(
namedExportDeclaration.exportClause.elements.concat(
factory.createExportSpecifier(
/*isTypeOnly*/ false,
/*propertyName*/ undefined,
node
)
)
)
),
/*moduleSpecifier*/ undefined,
/*assertClause*/ undefined
)
factory.updateExportDeclaration(
namedExportDeclaration,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.updateNamedExports(
namedExportDeclaration.exportClause,
sortSpecifiers(
namedExportDeclaration.exportClause.elements.concat(
factory.createExportSpecifier(
/*isTypeOnly*/ false,
/*propertyName*/ undefined,
node
)
)
)
),
/*moduleSpecifier*/ undefined,
/*assertClause*/ undefined
)

I think some of these arguments are self explanatory.

Copy link
Author

Choose a reason for hiding this comment

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

I'm happy to remove any of these you'd like, but tbh as someone new to the project => they're not :P

Honestly half the pain is having so many positional arguments at all!

Comment on lines 217 to 223
/*exportClause*/ factory.createNamedExports([
factory.createExportSpecifier(
/*isTypeOnly*/ false,
/*propertyName*/ undefined,
node
),
]),

Choose a reason for hiding this comment

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

Suggested change
/*exportClause*/ factory.createNamedExports([
factory.createExportSpecifier(
/*isTypeOnly*/ false,
/*propertyName*/ undefined,
node
),
]),
factory.createNamedExports([
factory.createExportSpecifier(
/*isTypeOnly*/ false,
/*propertyName*/ undefined,
node
),
]),

Comment on lines +788 to +789
/*expression*/ !(isVariableDeclarationList(node) && node.declarations.length !== 1),
/*message*/ "Only allow adding export modifier to variable lists with 1 element"

Choose a reason for hiding this comment

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

Suggested change
/*expression*/ !(isVariableDeclarationList(node) && node.declarations.length !== 1),
/*message*/ "Only allow adding export modifier to variable lists with 1 element"
!(isVariableDeclarationList(node) && node.declarations.length !== 1),
"Only allow adding export modifier to variable lists with 1 element"

////let d = 4;
////export function whatever() {
////}
////export { d }

Choose a reason for hiding this comment

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

Would be good to add some test cases where source file has some exports that we cannot use:

export type { a };
export { b } from "./b";
export * as c from "./c";
// ...

Copy link
Author

Choose a reason for hiding this comment

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

done

@@ -6598,7 +6598,14 @@
"category": "Message",
"code": 90058
},

"Export '{0}' from module '{1}'": {

Choose a reason for hiding this comment

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

Suggested change
"Export '{0}' from module '{1}'": {
"Export \"{0}\" from module \"{1}\"": {

minor, seems to match the other ones above this.

Copy link
Author

Choose a reason for hiding this comment

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

yup nice, done

Copy link
Author

Choose a reason for hiding this comment

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

hmm, the Remove import from './b' uses single quotes, what do you think?

Copy link
Author

Choose a reason for hiding this comment

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

it looks like there are only 4 instances using double quotes, that seems like a mistake
image
image

"category": "Message",
"code": 90059
},
"Add all missing exports": {

Choose a reason for hiding this comment

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

I realise this message is simialr to the "Add all imports" one, but I don't think it really expresses the change that will happen. Maybe "Export all missing members in modules"

Copy link
Author

Choose a reason for hiding this comment

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

Agreed, I changed to Export all missing members from modules


// @Filename: /b.ts
////declare function foo(): any;
////function bar(): any;

Choose a reason for hiding this comment

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

I would like to see some tests on overloaded functions as well.

Copy link
Author

Choose a reason for hiding this comment

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

Good shout! do you think the export should be on the implemented function or in a named export? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

Ok, I've given this a look and its actually a little tricky given the current implementation!

I can easily make all functions export using a named export => export {add};, but if we want to follow the convention of applying the export on the declaration where possible it'll take a bit more work! Do you think that's critical?

Choose a reason for hiding this comment

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

I think a separate export statement (e.g. export { add }) would be acceptable for function overloads.

index: 1,
description: `Export 'b' from module './a'`,
newFileContent: {
"/a.ts": `class a{}, export class b{};

Choose a reason for hiding this comment

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

This is producing invalid syntax. I think we need to fallback to export { b }; in this case.

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