Skip to content

Commit

Permalink
[New] no-named-as-default: ignore if imported obj is exported both …
Browse files Browse the repository at this point in the history
…as default and named
  • Loading branch information
akwodkiewicz committed Sep 8, 2024
1 parent ecc7b64 commit cd5e05c
Showing 1 changed file with 52 additions and 10 deletions.
62 changes: 52 additions & 10 deletions src/rules/no-named-as-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,70 @@ module.exports = {

create(context) {
function checkDefault(nameKey, defaultSpecifier) {
/**
* For ImportDefaultSpecifier we're interested in the "local" name (`foo` for `import {bar as foo} ...`)
* For ExportDefaultSpecifier we're interested in the "exported" name (`foo` for `export {bar as foo} ...`)
*/
const analyzedName = defaultSpecifier[nameKey].name;

// #566: default is a valid specifier
if (defaultSpecifier[nameKey].name === 'default') { return; }
if (analyzedName === 'default') { return; }

const declaration = importDeclaration(context);
/** @type {import('../exportMap').default | null} */
const importedModule = ExportMapBuilder.get(declaration.source.value, context);
if (importedModule == null) { return; }

const imports = ExportMapBuilder.get(declaration.source.value, context);
if (imports == null) { return; }
if (importedModule.errors.length) {
importedModule.reportErrors(context, declaration);
return;
}

if (imports.errors.length) {
imports.reportErrors(context, declaration);
if (!importedModule.hasDefault) {
// Impossible: how could we even get here? Kept in case of unexpected regressions.
return;
}

if (imports.has('default') && imports.has(defaultSpecifier[nameKey].name)) {
if (!importedModule.has(analyzedName)) {
// The name used locally for the default import was not even used in the imported module.
return;
}

context.report(
defaultSpecifier,
`Using exported name '${defaultSpecifier[nameKey].name}' as identifier for default export.`,
);
/**
* FIXME: We can verify if a default and a named export are pointing to the same symbol only
* if they are both `reexports`. In case one of the symbols is not a re-export, but defined
* in the file, the ExportMap structure has no info about what actually is being exported --
* the value in the `namespace` Map is an empty object.
*
* To solve this, it would require not relying on the ExportMap, but on some other way of
* accessing the imported module and its exported values.
*
* Additionally, although `ExportMap.get` is a unified way to get info from both `reexports`
* and `namespace` maps, it does not return valid output we need here, and I think this is
* related to the "cycle safeguards" in the `get` function.
*/

if (importedModule.reexports.has(analyzedName) && importedModule.reexports.has('default')) {
const thingImportedWithNamedImport = importedModule.reexports.get(analyzedName).getImport();
const thingImportedWithDefaultImport = importedModule.reexports.get('default').getImport();

// Case: both imports point to the same file and they both refer to the same symbol in this file.
if (
thingImportedWithNamedImport.path === thingImportedWithDefaultImport.path
&& thingImportedWithNamedImport.local === thingImportedWithDefaultImport.local
) {
// #1594: the imported module exports the same thing via a default export and a named export
return;
}
}

context.report(
defaultSpecifier,
`Using exported name '${defaultSpecifier[nameKey].name}' as identifier for default export.`,
);

}

return {
ImportDefaultSpecifier: checkDefault.bind(null, 'local'),
ExportDefaultSpecifier: checkDefault.bind(null, 'exported'),
Expand Down

0 comments on commit cd5e05c

Please sign in to comment.