@effect/language-service 0.22.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,8 +84,10 @@ Few options can be provided alongside the initialization of the Language Service
84
84
  },
85
85
  "quickinfo": true, // controls quickinfo over Effect (default: true)
86
86
  "completions": true, // controls Effect completions (default: true)
87
+ "goto": true, // controls Effect goto references (default: true)
87
88
  "allowedDuplicatedPackages": [], // list of package names that has effect in peer dependencies and are allowed to be duplicated (default: [])
88
- "namespaceImportPackages": [] // list of package names that should be preferred as imported with namespace imports e.g. ["effect"] (default: [])
89
+ "barrelImportPackages": [], // package names that should be preferred as imported from the top level barrel file (default: [])
90
+ "namespaceImportPackages": [] // package names that should be preferred as imported with namespace imports e.g. ["effect"] (default: [])
89
91
  }
90
92
  ]
91
93
  }
package/index.js CHANGED
@@ -1423,7 +1423,8 @@ function parse(config) {
1423
1423
  completions: isObject(config) && hasProperty(config, "completions") && isBoolean(config.completions) ? config.completions : true,
1424
1424
  goto: isObject(config) && hasProperty(config, "goto") && isBoolean(config.goto) ? config.goto : true,
1425
1425
  allowedDuplicatedPackages: isObject(config) && hasProperty(config, "allowedDuplicatedPackages") && isArray(config.allowedDuplicatedPackages) && config.allowedDuplicatedPackages.every(isString) ? config.allowedDuplicatedPackages.map((_) => _.toLowerCase()) : [],
1426
- namespaceImportPackages: isObject(config) && hasProperty(config, "namespaceImportPackages") && isArray(config.namespaceImportPackages) && config.namespaceImportPackages.every(isString) ? config.namespaceImportPackages.map((_) => _.toLowerCase()) : []
1426
+ namespaceImportPackages: isObject(config) && hasProperty(config, "namespaceImportPackages") && isArray(config.namespaceImportPackages) && config.namespaceImportPackages.every(isString) ? config.namespaceImportPackages.map((_) => _.toLowerCase()) : [],
1427
+ barrelImportPackages: isObject(config) && hasProperty(config, "barrelImportPackages") && isArray(config.barrelImportPackages) && config.barrelImportPackages.every(isString) ? config.barrelImportPackages.map((_) => _.toLowerCase()) : []
1427
1428
  };
1428
1429
  }
1429
1430
 
@@ -3484,7 +3485,7 @@ var completions = [
3484
3485
  effectDiagnosticsComment
3485
3486
  ];
3486
3487
 
3487
- // src/completions/middlewareNamespaceImports.ts
3488
+ // src/completions/middlewareAutoImports.ts
3488
3489
  var importablePackagesMetadataCache = /* @__PURE__ */ new Map();
3489
3490
  var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(function* (sourceFile) {
3490
3491
  const ts = yield* service(TypeScriptApi);
@@ -3494,7 +3495,18 @@ var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(functi
3494
3495
  const namespaceByFileName = /* @__PURE__ */ new Map();
3495
3496
  const excludedByFileName = /* @__PURE__ */ new Map();
3496
3497
  const unbarreledModulePathByFileName = /* @__PURE__ */ new Map();
3497
- for (const packageName of languageServicePluginOptions.namespaceImportPackages) {
3498
+ const barreledModulePathByFileName = /* @__PURE__ */ new Map();
3499
+ const packages = [
3500
+ ...languageServicePluginOptions.namespaceImportPackages.map((packageName) => ({
3501
+ packageName,
3502
+ kind: "namespace"
3503
+ })),
3504
+ ...languageServicePluginOptions.barrelImportPackages.map((packageName) => ({
3505
+ packageName,
3506
+ kind: "barrel"
3507
+ }))
3508
+ ];
3509
+ for (const { kind, packageName } of packages) {
3498
3510
  const barrelModule = ts.resolveModuleName(packageName, sourceFile.fileName, program.getCompilerOptions(), host);
3499
3511
  if (barrelModule.resolvedModule) {
3500
3512
  const barrelPath = barrelModule.resolvedModule.resolvedFileName;
@@ -3514,18 +3526,32 @@ var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(functi
3514
3526
  if (unbarreledModulePathResolved.resolvedModule) {
3515
3527
  const unbarreledModulePath = unbarreledModulePathResolved.resolvedModule.resolvedFileName;
3516
3528
  if (exportClause && ts.isNamespaceExport(exportClause) && ts.isIdentifier(exportClause.name)) {
3517
- namespaceByFileName.set(unbarreledModulePath, exportClause.name.text);
3518
- const existingUnbarreledModulePath = unbarreledModulePathByFileName.get(barrelSource.fileName) || [];
3519
- existingUnbarreledModulePath.push([exportClause.name.text, unbarreledModulePath]);
3520
- unbarreledModulePathByFileName.set(barrelSource.fileName, existingUnbarreledModulePath);
3529
+ if (kind === "namespace") {
3530
+ namespaceByFileName.set(unbarreledModulePath, exportClause.name.text);
3531
+ const existingUnbarreledModulePath = unbarreledModulePathByFileName.get(barrelSource.fileName) || [];
3532
+ existingUnbarreledModulePath.push({
3533
+ fileName: unbarreledModulePath,
3534
+ exportName: exportClause.name.text
3535
+ });
3536
+ unbarreledModulePathByFileName.set(barrelSource.fileName, existingUnbarreledModulePath);
3537
+ }
3538
+ if (kind === "barrel") {
3539
+ barreledModulePathByFileName.set(unbarreledModulePath, {
3540
+ fileName: barrelSource.fileName,
3541
+ exportName: exportClause.name.text,
3542
+ packageName
3543
+ });
3544
+ }
3521
3545
  }
3522
3546
  if (exportClause && ts.isNamedExports(exportClause)) {
3523
3547
  for (const element of exportClause.elements) {
3524
3548
  if (!ts.isIdentifier(element.name)) continue;
3525
3549
  const methodName = element.name.text;
3526
- const excludedMethods = excludedByFileName.get(methodName) || [];
3527
- excludedMethods.push(unbarreledModulePath);
3528
- excludedByFileName.set(methodName, excludedMethods);
3550
+ if (kind === "namespace") {
3551
+ const excludedMethods = excludedByFileName.get(methodName) || [];
3552
+ excludedMethods.push(unbarreledModulePath);
3553
+ excludedByFileName.set(methodName, excludedMethods);
3554
+ }
3529
3555
  }
3530
3556
  }
3531
3557
  }
@@ -3538,141 +3564,252 @@ var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(functi
3538
3564
  return {
3539
3565
  getImportNamespaceByFileName: (fileName) => namespaceByFileName.get(fileName),
3540
3566
  isExcludedFromNamespaceImport: (fileName, exportName) => (excludedByFileName.get(exportName) || []).includes(fileName),
3541
- getUnbarreledModulePath: (fileName, exportName) => unbarreledModulePathByFileName.get(fileName)?.find(([name]) => name === exportName)?.[1]
3567
+ getUnbarreledModulePath: (fileName, exportName) => unbarreledModulePathByFileName.get(fileName)?.find((_) => _.exportName === exportName)?.fileName,
3568
+ getBarreledModulePath: (fileName) => barreledModulePathByFileName.get(fileName)
3542
3569
  };
3543
3570
  });
3544
- var appendEffectCompletionEntryData = fn("collectNamespaceImports")(
3545
- function* (sourceFile, applicableCompletions) {
3571
+ var appendEffectCompletionEntryData = fn("appendEffectCompletionEntryData")(
3572
+ function* (_sourceFile, applicableCompletions) {
3546
3573
  const languageServicePluginOptions = yield* service(LanguageServicePluginOptions);
3547
- if (languageServicePluginOptions.namespaceImportPackages.length === 0) return applicableCompletions;
3548
- const packagesMetadata = importablePackagesMetadataCache.get(sourceFile.fileName) || (yield* makeImportablePackagesMetadata(sourceFile));
3549
- importablePackagesMetadataCache.set(sourceFile.fileName, packagesMetadata);
3574
+ if (languageServicePluginOptions.namespaceImportPackages.length === 0 && languageServicePluginOptions.barrelImportPackages.length === 0) return applicableCompletions;
3550
3575
  if (applicableCompletions) {
3551
3576
  return {
3552
3577
  ...applicableCompletions,
3553
- entries: applicableCompletions.entries.map((entry) => {
3554
- if (entry.data && entry.data.fileName && !entry.insertText && !entry.filterText && entry.data.exportName && entry.data.moduleSpecifier) {
3555
- const isExcluded = packagesMetadata.isExcludedFromNamespaceImport(
3556
- entry.data.fileName,
3557
- entry.data.exportName
3558
- );
3559
- if (isExcluded) return entry;
3560
- const unbarreledModulePath = packagesMetadata.getUnbarreledModulePath(
3561
- entry.data.fileName,
3562
- entry.data.exportName
3563
- );
3564
- const namespaceName = packagesMetadata.getImportNamespaceByFileName(
3565
- unbarreledModulePath || entry.data.fileName
3566
- );
3567
- if (namespaceName) {
3568
- return {
3569
- ...entry,
3570
- // insertText: unbarreledModulePath ? namespaceName : namespaceName + "." + entry.name,
3571
- // filterText: entry.name,
3572
- data: {
3573
- ...entry.data,
3574
- effectNamespaceName: namespaceName,
3575
- effectUnbarreledModulePath: unbarreledModulePath || "",
3576
- effectReplaceSpan: entry.replacementSpan || applicableCompletions.optionalReplacementSpan
3577
- }
3578
- };
3578
+ entries: applicableCompletions.entries.map(
3579
+ (entry) => entry.data ? {
3580
+ ...entry,
3581
+ data: {
3582
+ ...entry.data,
3583
+ effectReplaceSpan: entry.replacementSpan || applicableCompletions.optionalReplacementSpan
3579
3584
  }
3580
- }
3581
- return entry;
3582
- })
3585
+ } : entry
3586
+ )
3583
3587
  };
3584
3588
  }
3585
3589
  return applicableCompletions;
3586
3590
  }
3587
3591
  );
3592
+ var isAutoImportOnlyCodeActions = fn("isAutoImportOnlyCodeActions")(
3593
+ function* (sourceFile, codeActions, exportName) {
3594
+ const ts = yield* service(TypeScriptApi);
3595
+ if (!codeActions) return;
3596
+ if (codeActions.length !== 1) return;
3597
+ const action = codeActions[0];
3598
+ const changes = action.changes;
3599
+ if (changes.length !== 1) return;
3600
+ const fileTextChanges = action.changes[0];
3601
+ if (fileTextChanges.fileName !== sourceFile.fileName) return;
3602
+ const textChanges = fileTextChanges.textChanges;
3603
+ if (textChanges.length !== 1) return;
3604
+ const change = textChanges[0];
3605
+ if (change.newText.trim().toLowerCase().startsWith("import") && change.newText.indexOf(exportName) > -1) {
3606
+ return {
3607
+ type: "create"
3608
+ };
3609
+ }
3610
+ if (change.newText.indexOf(exportName) > -1) {
3611
+ const ancestorNodes = yield* getAncestorNodesInRange(sourceFile, {
3612
+ pos: change.span.start,
3613
+ end: change.span.start
3614
+ });
3615
+ const importNodes = ancestorNodes.filter((node) => ts.isImportDeclaration(node));
3616
+ if (importNodes.length > 0) {
3617
+ return {
3618
+ type: "update"
3619
+ };
3620
+ }
3621
+ }
3622
+ }
3623
+ );
3624
+ var getImportFromNamespaceCodeActions = fn("getImportFromNamespaceCodeActions")(function* (formatOptions, preferences, languageServiceHost, sourceFile, effectReplaceSpan, effectNamespaceName, effectUnbarreledModulePath, newModuleSpecifier) {
3625
+ const ts = yield* service(TypeScriptApi);
3626
+ const formatContext = ts.formatting.getFormatContext(
3627
+ formatOptions || {},
3628
+ languageServiceHost
3629
+ );
3630
+ const changes = ts.textChanges.ChangeTracker.with(
3631
+ {
3632
+ formatContext,
3633
+ host: languageServiceHost,
3634
+ preferences: preferences || {}
3635
+ },
3636
+ (changeTracker) => {
3637
+ ts.insertImports(
3638
+ changeTracker,
3639
+ sourceFile,
3640
+ ts.factory.createImportDeclaration(
3641
+ void 0,
3642
+ ts.factory.createImportClause(
3643
+ false,
3644
+ void 0,
3645
+ ts.factory.createNamespaceImport(ts.factory.createIdentifier(effectNamespaceName))
3646
+ ),
3647
+ ts.factory.createStringLiteral(newModuleSpecifier)
3648
+ ),
3649
+ true,
3650
+ preferences || {}
3651
+ );
3652
+ if (!effectUnbarreledModulePath) {
3653
+ changeTracker.insertText(
3654
+ sourceFile,
3655
+ effectReplaceSpan.start,
3656
+ effectNamespaceName + "."
3657
+ );
3658
+ }
3659
+ }
3660
+ );
3661
+ return [
3662
+ {
3663
+ description: "Import * as " + effectNamespaceName + " from " + newModuleSpecifier,
3664
+ changes
3665
+ }
3666
+ ];
3667
+ });
3668
+ var getImportFromBarrelCodeActions = fn("getImportFromBarrelCodeActions")(function* (formatOptions, preferences, languageServiceHost, sourceFile, effectReplaceSpan, newModuleSpecifier, barrelExportName) {
3669
+ const ts = yield* service(TypeScriptApi);
3670
+ const formatContext = ts.formatting.getFormatContext(
3671
+ formatOptions || {},
3672
+ languageServiceHost
3673
+ );
3674
+ const changes = ts.textChanges.ChangeTracker.with(
3675
+ {
3676
+ formatContext,
3677
+ host: languageServiceHost,
3678
+ preferences: preferences || {}
3679
+ },
3680
+ (changeTracker) => {
3681
+ let foundImportDeclaration = false;
3682
+ for (const statement of sourceFile.statements) {
3683
+ if (ts.isImportDeclaration(statement)) {
3684
+ const moduleSpecifier = statement.moduleSpecifier;
3685
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === newModuleSpecifier) {
3686
+ const importClause = statement.importClause;
3687
+ if (importClause && importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
3688
+ const namedImports = importClause.namedBindings;
3689
+ changeTracker.replaceNode(
3690
+ sourceFile,
3691
+ namedImports,
3692
+ ts.factory.createNamedImports(
3693
+ namedImports.elements.concat([
3694
+ ts.factory.createImportSpecifier(false, void 0, ts.factory.createIdentifier(barrelExportName))
3695
+ ])
3696
+ )
3697
+ );
3698
+ foundImportDeclaration = true;
3699
+ break;
3700
+ }
3701
+ }
3702
+ }
3703
+ }
3704
+ if (!foundImportDeclaration) {
3705
+ ts.insertImports(
3706
+ changeTracker,
3707
+ sourceFile,
3708
+ ts.factory.createImportDeclaration(
3709
+ void 0,
3710
+ ts.factory.createImportClause(
3711
+ false,
3712
+ void 0,
3713
+ ts.factory.createNamedImports(
3714
+ [
3715
+ ts.factory.createImportSpecifier(false, void 0, ts.factory.createIdentifier(barrelExportName))
3716
+ ]
3717
+ )
3718
+ ),
3719
+ ts.factory.createStringLiteral(newModuleSpecifier)
3720
+ ),
3721
+ true,
3722
+ preferences || {}
3723
+ );
3724
+ }
3725
+ changeTracker.insertText(
3726
+ sourceFile,
3727
+ effectReplaceSpan.start,
3728
+ barrelExportName + "."
3729
+ );
3730
+ }
3731
+ );
3732
+ return [
3733
+ {
3734
+ description: "Import { " + barrelExportName + " } from " + newModuleSpecifier,
3735
+ changes
3736
+ }
3737
+ ];
3738
+ });
3588
3739
  var postprocessCompletionEntryDetails = fn("postprocessCompletionEntryDetails")(
3589
3740
  function* (sourceFile, data, applicableCompletionEntryDetails, formatOptions, preferences, languageServiceHost) {
3590
3741
  const languageServicePluginOptions = yield* service(LanguageServicePluginOptions);
3591
- if (languageServicePluginOptions.namespaceImportPackages.length === 0) return applicableCompletionEntryDetails;
3742
+ if (languageServicePluginOptions.namespaceImportPackages.length === 0 && languageServicePluginOptions.barrelImportPackages.length === 0) return applicableCompletionEntryDetails;
3592
3743
  const ts = yield* service(TypeScriptApi);
3593
3744
  const program = yield* service(TypeScriptProgram);
3594
3745
  const getModuleSpecifier = makeGetModuleSpecifier(ts);
3595
3746
  if (!getModuleSpecifier) return applicableCompletionEntryDetails;
3596
3747
  if (!applicableCompletionEntryDetails) return applicableCompletionEntryDetails;
3597
3748
  if (!data) return applicableCompletionEntryDetails;
3598
- if (!("effectNamespaceName" in data && "effectUnbarreledModulePath" in data && "effectReplaceSpan" in data)) {
3599
- return applicableCompletionEntryDetails;
3600
- }
3749
+ const { exportName, fileName, moduleSpecifier } = data;
3750
+ if (!fileName) return applicableCompletionEntryDetails;
3751
+ if (!exportName) return applicableCompletionEntryDetails;
3752
+ if (!moduleSpecifier) return applicableCompletionEntryDetails;
3753
+ if (!("effectReplaceSpan" in data)) return applicableCompletionEntryDetails;
3601
3754
  const effectReplaceSpan = data.effectReplaceSpan;
3602
- const codeActions = applicableCompletionEntryDetails.codeActions;
3603
- if (codeActions && codeActions.length === 1) {
3604
- const action = codeActions[0];
3605
- if (action.changes.length === 1) {
3606
- const fileTextChanges = action.changes[0];
3607
- if (fileTextChanges.fileName === sourceFile.fileName && fileTextChanges.textChanges.length === 1) {
3608
- const change = fileTextChanges.textChanges[0];
3609
- let hasImportActions = false;
3610
- if (change.newText.trim().toLowerCase().startsWith("import") && change.newText.indexOf(data.exportName) > -1) {
3611
- hasImportActions = true;
3612
- }
3613
- if (!hasImportActions && change.newText.indexOf(data.exportName) > -1) {
3614
- const ancestorNodes = yield* getAncestorNodesInRange(sourceFile, {
3615
- pos: change.span.start,
3616
- end: change.span.start
3617
- });
3618
- const importNodes = ancestorNodes.filter((node) => ts.isImportDeclaration(node));
3619
- hasImportActions = importNodes.length > 0;
3620
- }
3621
- if (!hasImportActions) return applicableCompletionEntryDetails;
3622
- const formatContext = ts.formatting.getFormatContext(
3623
- formatOptions || {},
3624
- languageServiceHost
3625
- );
3626
- const changes = ts.textChanges.ChangeTracker.with(
3627
- {
3628
- formatContext,
3629
- host: languageServiceHost,
3630
- preferences: preferences || {}
3631
- },
3632
- (changeTracker) => {
3633
- const isBarrelRedirect = String(data.effectUnbarreledModulePath).length > 0;
3634
- const moduleSpecifier = isBarrelRedirect ? getModuleSpecifier(
3635
- program.getCompilerOptions(),
3636
- sourceFile,
3637
- sourceFile.fileName,
3638
- String(data.effectUnbarreledModulePath),
3639
- program
3640
- ) : String(data.moduleSpecifier);
3641
- ts.insertImports(
3642
- changeTracker,
3643
- sourceFile,
3644
- ts.factory.createImportDeclaration(
3645
- void 0,
3646
- ts.factory.createImportClause(
3647
- false,
3648
- void 0,
3649
- ts.factory.createNamespaceImport(ts.factory.createIdentifier(String(data.effectNamespaceName)))
3650
- ),
3651
- ts.factory.createStringLiteral(moduleSpecifier)
3652
- ),
3653
- true,
3654
- preferences || {}
3655
- );
3656
- if (!isBarrelRedirect) {
3657
- changeTracker.insertText(
3658
- sourceFile,
3659
- effectReplaceSpan.start,
3660
- String(data.effectNamespaceName) + "."
3661
- );
3662
- }
3663
- }
3664
- );
3665
- return {
3666
- ...applicableCompletionEntryDetails,
3667
- codeActions: [
3668
- {
3669
- description: "Import * as " + data.effectNamespaceName + " from " + data.effectUnbarreledModulePath,
3670
- changes
3671
- }
3672
- ]
3673
- };
3674
- }
3675
- }
3755
+ const result = yield* isAutoImportOnlyCodeActions(
3756
+ sourceFile,
3757
+ applicableCompletionEntryDetails.codeActions,
3758
+ exportName
3759
+ );
3760
+ if (!result) return applicableCompletionEntryDetails;
3761
+ const packagesMetadata = importablePackagesMetadataCache.get(sourceFile.fileName) || (yield* makeImportablePackagesMetadata(sourceFile));
3762
+ importablePackagesMetadataCache.set(sourceFile.fileName, packagesMetadata);
3763
+ const isExcluded = packagesMetadata.isExcludedFromNamespaceImport(
3764
+ fileName,
3765
+ exportName
3766
+ );
3767
+ if (isExcluded) return applicableCompletionEntryDetails;
3768
+ const asBarrelImport = packagesMetadata.getBarreledModulePath(fileName);
3769
+ if (asBarrelImport) {
3770
+ const codeActions = yield* getImportFromBarrelCodeActions(
3771
+ formatOptions,
3772
+ preferences,
3773
+ languageServiceHost,
3774
+ sourceFile,
3775
+ effectReplaceSpan,
3776
+ asBarrelImport.packageName,
3777
+ asBarrelImport.exportName
3778
+ );
3779
+ return {
3780
+ ...applicableCompletionEntryDetails,
3781
+ codeActions
3782
+ };
3783
+ }
3784
+ const effectUnbarreledModulePath = packagesMetadata.getUnbarreledModulePath(
3785
+ fileName,
3786
+ exportName
3787
+ );
3788
+ const asNamespaceImport = packagesMetadata.getImportNamespaceByFileName(
3789
+ effectUnbarreledModulePath || fileName
3790
+ );
3791
+ if (asNamespaceImport) {
3792
+ const newModuleSpecifier = effectUnbarreledModulePath ? getModuleSpecifier(
3793
+ program.getCompilerOptions(),
3794
+ sourceFile,
3795
+ sourceFile.fileName,
3796
+ String(effectUnbarreledModulePath || fileName),
3797
+ program
3798
+ ) : moduleSpecifier;
3799
+ const codeActions = yield* getImportFromNamespaceCodeActions(
3800
+ formatOptions,
3801
+ preferences,
3802
+ languageServiceHost,
3803
+ sourceFile,
3804
+ effectReplaceSpan,
3805
+ asNamespaceImport,
3806
+ effectUnbarreledModulePath,
3807
+ newModuleSpecifier
3808
+ );
3809
+ return {
3810
+ ...applicableCompletionEntryDetails,
3811
+ codeActions
3812
+ };
3676
3813
  }
3677
3814
  return applicableCompletionEntryDetails;
3678
3815
  }