@@ -25,14 +25,24 @@ const jscs = jscodeshiftDefault || jscodeshiftNamespace;
2525
2626// These are types not in the TS sense, but in the instance-of-a-Type-class sense
2727const {
28+ ArrayPattern,
29+ ClassDeclaration,
30+ ExportAllDeclaration,
31+ ExportDefaultDeclaration,
32+ ExportDefaultSpecifier,
33+ ExportNamedDeclaration,
2834 ExportSpecifier,
35+ FunctionDeclaration,
2936 Identifier,
3037 ImportSpecifier,
38+ JSXIdentifier,
3139 MemberExpression,
3240 Node,
3341 ObjectExpression,
3442 ObjectPattern,
3543 Property,
44+ RestElement,
45+ TSTypeParameter,
3646 VariableDeclaration,
3747 VariableDeclarator,
3848} = jscs ;
@@ -291,8 +301,6 @@ function maybeRenameNode(ast: AST, identifierPath: ASTPath<IdentifierNode>, alia
291301 // it means we potentially need to rename something *not* already named `getServerSideProps`, `getStaticProps`, or
292302 // `getStaticPaths`, meaning we need to rename nodes outside of the collection upon which we're currently acting.
293303 if ( ExportSpecifier . check ( parent ) ) {
294- // console.log(node);
295- // debugger;
296304 if ( parent . exported . name !== parent . local ?. name && node === parent . exported ) {
297305 const currentLocalName = parent . local ?. name || '' ;
298306 renameIdentifiers ( ast , currentLocalName , alias ) ;
@@ -320,3 +328,202 @@ export function removeComments(ast: AST): void {
320328 const nodesWithComments = ast . find ( Node ) . filter ( nodePath => ! ! nodePath . node . comments ) ;
321329 nodesWithComments . forEach ( nodePath => ( nodePath . node . comments = null ) ) ;
322330}
331+
332+ /**
333+ * Determines from a given AST of a file whether the file has a default export or not.
334+ */
335+ export function hasDefaultExport ( ast : AST ) : boolean {
336+ const defaultExports = ast . find ( Node , value => {
337+ return (
338+ ExportDefaultDeclaration . check ( value ) ||
339+ ExportDefaultSpecifier . check ( value ) ||
340+ ( ExportSpecifier . check ( value ) && value . exported . name === 'default' )
341+ ) ;
342+ } ) ;
343+
344+ // In theory there should only ever be 0 or 1, but who knows what people do
345+ return defaultExports . length > 0 ;
346+ }
347+
348+ /**
349+ * Extracts all identifier names (`'constName'`) from an destructuringassignment'sArrayPattern (the `[constName]` in`const [constName] = [1]`).
350+ *
351+ * This function recursively calls itself and `getExportIdentifiersFromObjectPattern` since destructuring assignments
352+ * can be deeply nested with objects and arrays.
353+ *
354+ * Example - take the following program:
355+ *
356+ * ```js
357+ * export const [{ foo: name1 }, [{ bar: [name2]}, name3]] = [{ foo: 1 }, [{ bar: [2] }, 3]];
358+ * ```
359+ *
360+ * The `ArrayPattern` node in question for this program is the left hand side of the assignment:
361+ * `[{ foo: name1 }, [{ bar: [name2]}, name3]]`
362+ *
363+ * Applying this function to this `ArrayPattern` will return the following: `["name1", "name2", "name3"]`
364+ *
365+ * DISCLAIMER: This function only correcly extracts identifiers of `ArrayPatterns` in the context of export statements.
366+ * Using this for `ArrayPattern` outside of exports would require us to handle more edgecases. Hence the "Export" in
367+ * this function's name.
368+ */
369+ function getExportIdentifiersFromArrayPattern ( arrayPattern : jscsTypes . ArrayPattern ) : string [ ] {
370+ const identifiers : string [ ] = [ ] ;
371+
372+ arrayPattern . elements . forEach ( element => {
373+ if ( Identifier . check ( element ) ) {
374+ identifiers . push ( element . name ) ;
375+ } else if ( ObjectPattern . check ( element ) ) {
376+ identifiers . push ( ...getExportIdentifiersFromObjectPattern ( element ) ) ;
377+ } else if ( ArrayPattern . check ( element ) ) {
378+ identifiers . push ( ...getExportIdentifiersFromArrayPattern ( element ) ) ;
379+ } else if ( RestElement . check ( element ) && Identifier . check ( element . argument ) ) {
380+ // `RestElements` are spread operators
381+ identifiers . push ( element . argument . name ) ;
382+ }
383+ } ) ;
384+
385+ return identifiers ;
386+ }
387+
388+ /**
389+ * Grabs all identifiers from an ObjectPattern within a destructured named export declaration
390+ * statement (`name` in "export const { val: name } = { val: 1 }").
391+ *
392+ * This function recursively calls itself and `getExportIdentifiersFromArrayPattern` since destructuring assignments
393+ * can be deeply nested with objects and arrays.
394+ *
395+ * Example - take the following program:
396+ *
397+ * ```js
398+ * export const { foo: [{ bar: name1 }], name2, ...name3 } = { foo: [{}] };
399+ * ```
400+ *
401+ * The `ObjectPattern` node in question for this program is the left hand side of the assignment:
402+ * `{ foo: [{ bar: name1 }], name2, ...name3 } = { foo: [{}] }`
403+ *
404+ * Applying this function to this `ObjectPattern` will return the following: `["name1", "name2", "name3"]`
405+ *
406+ * DISCLAIMER: This function only correcly extracts identifiers of `ObjectPatterns` in the context of export statements.
407+ * Using this for `ObjectPatterns` outside of exports would require us to handle more edgecases. Hence the "Export" in
408+ * this function's name.
409+ */
410+ function getExportIdentifiersFromObjectPattern ( objectPatternNode : jscsTypes . ObjectPattern ) : string [ ] {
411+ const identifiers : string [ ] = [ ] ;
412+
413+ objectPatternNode . properties . forEach ( property => {
414+ // An `ObjectPattern`'s properties can be either `Property`s or `RestElement`s.
415+ if ( Property . check ( property ) ) {
416+ if ( Identifier . check ( property . value ) ) {
417+ identifiers . push ( property . value . name ) ;
418+ } else if ( ObjectPattern . check ( property . value ) ) {
419+ identifiers . push ( ...getExportIdentifiersFromObjectPattern ( property . value ) ) ;
420+ } else if ( ArrayPattern . check ( property . value ) ) {
421+ identifiers . push ( ...getExportIdentifiersFromArrayPattern ( property . value ) ) ;
422+ } else if ( RestElement . check ( property . value ) && Identifier . check ( property . value . argument ) ) {
423+ // `RestElements` are spread operators
424+ identifiers . push ( property . value . argument . name ) ;
425+ }
426+ // @ts -ignore AST types are wrong here
427+ } else if ( RestElement . check ( property ) && Identifier . check ( property . argument ) ) {
428+ // `RestElements` are spread operators
429+ // @ts -ignore AST types are wrong here
430+ identifiers . push ( property . argument . name as string ) ;
431+ }
432+ } ) ;
433+
434+ return identifiers ;
435+ }
436+
437+ /**
438+ * Given the AST of a file, this function extracts all named exports from the file.
439+ *
440+ * @returns a list of deduplicated identifiers.
441+ */
442+ export function getExportIdentifierNames ( ast : AST ) : string [ ] {
443+ // We'll use a set to dedupe at the end, but for now we use an array as our accumulator because you can add multiple elements to it at once.
444+ const identifiers : string [ ] = [ ] ;
445+
446+ // The following variable collects all export statements that double as named declaration, e.g.:
447+ // - export function myFunc() {}
448+ // - export var myVar = 1337
449+ // - export const myConst = 1337
450+ // - export const { a, ..rest } = { a: 1, b: 2, c: 3 }
451+ // We will narrow those situations down in subsequent code blocks.
452+ const namedExportDeclarationNodeDeclarations = ast
453+ . find ( ExportNamedDeclaration )
454+ . nodes ( )
455+ . map ( namedExportDeclarationNode => namedExportDeclarationNode . declaration ) ;
456+
457+ namedExportDeclarationNodeDeclarations
458+ . filter ( ( declarationNode ) : declarationNode is jscsTypes . VariableDeclaration =>
459+ // Narrow down to varible declarations, e.g.:
460+ // export const a = ...;
461+ // export var b = ...;
462+ // export let c = ...;
463+ // export let c, d = 1;
464+ VariableDeclaration . check ( declarationNode ) ,
465+ )
466+ . map (
467+ variableDeclarationNode =>
468+ // Grab all declarations in a single export statement.
469+ // There can be multiple in the case of for example in `export let a, b;`.
470+ variableDeclarationNode . declarations ,
471+ )
472+ . reduce ( ( prev , curr ) => [ ...prev , ...curr ] , [ ] ) // flatten - now we have all declaration nodes in one flat array
473+ . forEach ( declarationNode => {
474+ if (
475+ Identifier . check ( declarationNode ) || // should never happen
476+ JSXIdentifier . check ( declarationNode ) || // JSX like `<name></name>` - we don't care about these
477+ TSTypeParameter . check ( declarationNode ) // type definitions - we don't care about those
478+ ) {
479+ // We should never have to enter this branch, it is just for type narrowing.
480+ } else if ( Identifier . check ( declarationNode . id ) ) {
481+ // If it's a simple declaration with an identifier we collect it. (e.g. `const myIdentifier = 1;` -> "myIdentifier")
482+ identifiers . push ( declarationNode . id . name ) ;
483+ } else if ( ObjectPattern . check ( declarationNode . id ) ) {
484+ // If we encounter a destructuring export like `export const { foo: name1, bar: name2 } = { foo: 1, bar: 2 };`,
485+ // we try collecting the identifiers from the pattern `{ foo: name1, bar: name2 }`.
486+ identifiers . push ( ...getExportIdentifiersFromObjectPattern ( declarationNode . id ) ) ;
487+ } else if ( ArrayPattern . check ( declarationNode . id ) ) {
488+ // If we encounter a destructuring export like `export const [name1, name2] = [1, 2];`,
489+ // we try collecting the identifiers from the pattern `[name1, name2]`.
490+ identifiers . push ( ...getExportIdentifiersFromArrayPattern ( declarationNode . id ) ) ;
491+ }
492+ } ) ;
493+
494+ namedExportDeclarationNodeDeclarations
495+ . filter (
496+ // Narrow down to class and function declarations, e.g.:
497+ // export class Foo {};
498+ // export function bar() {};
499+ ( declarationNode ) : declarationNode is jscsTypes . ClassDeclaration | jscsTypes . FunctionDeclaration =>
500+ ClassDeclaration . check ( declarationNode ) || FunctionDeclaration . check ( declarationNode ) ,
501+ )
502+ . map ( node => node . id ) // Grab the identifier of the function/class - Note: it might be `null` when it's anonymous
503+ . filter ( ( id ) : id is jscsTypes . Identifier => Identifier . check ( id ) ) // Elaborate way of null-checking
504+ . forEach ( id => identifiers . push ( id . name ) ) ; // Collect the name of the identifier
505+
506+ ast
507+ . find ( ExportSpecifier ) // Find stuff like `export {<id [as name]>} [from ...];`
508+ . nodes ( )
509+ . forEach ( specifier => {
510+ // Taking the example above `specifier.exported.name` always contains `id` unless `name` is specified, then it's `name`;
511+ if ( specifier . exported . name !== 'default' ) {
512+ // You can do default exports "export { something as default };" but we do not want to collect "default" in this
513+ // function since it only wants to collect named exports.
514+ identifiers . push ( specifier . exported . name ) ;
515+ }
516+ } ) ;
517+
518+ ast
519+ . find ( ExportAllDeclaration ) // Find stuff like `export * from ..." and "export * as someVariable from ...`
520+ . nodes ( )
521+ . forEach ( declaration => {
522+ // Narrow it down to only find `export * as someVariable from ...` (emphasis on "as someVariable")
523+ if ( declaration . exported ) {
524+ identifiers . push ( declaration . exported . name ) ; // `declaration.exported.name` contains "someVariable"
525+ }
526+ } ) ;
527+
528+ return [ ...new Set ( identifiers ) ] ; // dedupe
529+ }
0 commit comments