@effect/language-service 0.22.3 → 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);
@@ -3495,7 +3496,17 @@ var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(functi
3495
3496
  const excludedByFileName = /* @__PURE__ */ new Map();
3496
3497
  const unbarreledModulePathByFileName = /* @__PURE__ */ new Map();
3497
3498
  const barreledModulePathByFileName = /* @__PURE__ */ new Map();
3498
- for (const packageName of languageServicePluginOptions.namespaceImportPackages) {
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) {
3499
3510
  const barrelModule = ts.resolveModuleName(packageName, sourceFile.fileName, program.getCompilerOptions(), host);
3500
3511
  if (barrelModule.resolvedModule) {
3501
3512
  const barrelPath = barrelModule.resolvedModule.resolvedFileName;
@@ -3515,25 +3526,32 @@ var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(functi
3515
3526
  if (unbarreledModulePathResolved.resolvedModule) {
3516
3527
  const unbarreledModulePath = unbarreledModulePathResolved.resolvedModule.resolvedFileName;
3517
3528
  if (exportClause && ts.isNamespaceExport(exportClause) && ts.isIdentifier(exportClause.name)) {
3518
- namespaceByFileName.set(unbarreledModulePath, exportClause.name.text);
3519
- const existingUnbarreledModulePath = unbarreledModulePathByFileName.get(barrelSource.fileName) || [];
3520
- existingUnbarreledModulePath.push({
3521
- fileName: unbarreledModulePath,
3522
- exportName: exportClause.name.text
3523
- });
3524
- unbarreledModulePathByFileName.set(barrelSource.fileName, existingUnbarreledModulePath);
3525
- barreledModulePathByFileName.set(unbarreledModulePath, {
3526
- fileName: barrelSource.fileName,
3527
- exportName: exportClause.name.text
3528
- });
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
+ }
3529
3545
  }
3530
3546
  if (exportClause && ts.isNamedExports(exportClause)) {
3531
3547
  for (const element of exportClause.elements) {
3532
3548
  if (!ts.isIdentifier(element.name)) continue;
3533
3549
  const methodName = element.name.text;
3534
- const excludedMethods = excludedByFileName.get(methodName) || [];
3535
- excludedMethods.push(unbarreledModulePath);
3536
- excludedByFileName.set(methodName, excludedMethods);
3550
+ if (kind === "namespace") {
3551
+ const excludedMethods = excludedByFileName.get(methodName) || [];
3552
+ excludedMethods.push(unbarreledModulePath);
3553
+ excludedByFileName.set(methodName, excludedMethods);
3554
+ }
3537
3555
  }
3538
3556
  }
3539
3557
  }
@@ -3553,7 +3571,7 @@ var makeImportablePackagesMetadata = fn("makeImportablePackagesMetadata")(functi
3553
3571
  var appendEffectCompletionEntryData = fn("appendEffectCompletionEntryData")(
3554
3572
  function* (_sourceFile, applicableCompletions) {
3555
3573
  const languageServicePluginOptions = yield* service(LanguageServicePluginOptions);
3556
- if (languageServicePluginOptions.namespaceImportPackages.length === 0) return applicableCompletions;
3574
+ if (languageServicePluginOptions.namespaceImportPackages.length === 0 && languageServicePluginOptions.barrelImportPackages.length === 0) return applicableCompletions;
3557
3575
  if (applicableCompletions) {
3558
3576
  return {
3559
3577
  ...applicableCompletions,
@@ -3571,41 +3589,157 @@ var appendEffectCompletionEntryData = fn("appendEffectCompletionEntryData")(
3571
3589
  return applicableCompletions;
3572
3590
  }
3573
3591
  );
3574
- var postprocessCompletionEntryDetails = fn("postprocessCompletionEntryDetails")(
3575
- function* (sourceFile, data, applicableCompletionEntryDetails, formatOptions, preferences, languageServiceHost) {
3576
- const languageServicePluginOptions = yield* service(LanguageServicePluginOptions);
3577
- if (languageServicePluginOptions.namespaceImportPackages.length === 0) return applicableCompletionEntryDetails;
3578
- const isAutoImportOnlyCodeActions = fn("isAutoImportOnlyCodeActions")(
3579
- function* (codeActions, exportName2) {
3580
- if (!codeActions) return;
3581
- if (codeActions.length !== 1) return;
3582
- const action = codeActions[0];
3583
- const changes2 = action.changes;
3584
- if (changes2.length !== 1) return;
3585
- const fileTextChanges = action.changes[0];
3586
- if (fileTextChanges.fileName !== sourceFile.fileName) return;
3587
- const textChanges = fileTextChanges.textChanges;
3588
- if (textChanges.length !== 1) return;
3589
- const change = textChanges[0];
3590
- if (change.newText.trim().toLowerCase().startsWith("import") && change.newText.indexOf(exportName2) > -1) {
3591
- return {
3592
- type: "create"
3593
- };
3594
- }
3595
- if (change.newText.indexOf(exportName2) > -1) {
3596
- const ancestorNodes = yield* getAncestorNodesInRange(sourceFile, {
3597
- pos: change.span.start,
3598
- end: change.span.start
3599
- });
3600
- const importNodes = ancestorNodes.filter((node) => ts.isImportDeclaration(node));
3601
- if (importNodes.length > 0) {
3602
- return {
3603
- type: "update"
3604
- };
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
+ }
3605
3701
  }
3606
3702
  }
3607
3703
  }
3608
- );
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
+ });
3739
+ var postprocessCompletionEntryDetails = fn("postprocessCompletionEntryDetails")(
3740
+ function* (sourceFile, data, applicableCompletionEntryDetails, formatOptions, preferences, languageServiceHost) {
3741
+ const languageServicePluginOptions = yield* service(LanguageServicePluginOptions);
3742
+ if (languageServicePluginOptions.namespaceImportPackages.length === 0 && languageServicePluginOptions.barrelImportPackages.length === 0) return applicableCompletionEntryDetails;
3609
3743
  const ts = yield* service(TypeScriptApi);
3610
3744
  const program = yield* service(TypeScriptProgram);
3611
3745
  const getModuleSpecifier = makeGetModuleSpecifier(ts);
@@ -3618,74 +3752,66 @@ var postprocessCompletionEntryDetails = fn("postprocessCompletionEntryDetails")(
3618
3752
  if (!moduleSpecifier) return applicableCompletionEntryDetails;
3619
3753
  if (!("effectReplaceSpan" in data)) return applicableCompletionEntryDetails;
3620
3754
  const effectReplaceSpan = data.effectReplaceSpan;
3621
- const result = yield* isAutoImportOnlyCodeActions(applicableCompletionEntryDetails.codeActions, exportName);
3755
+ const result = yield* isAutoImportOnlyCodeActions(
3756
+ sourceFile,
3757
+ applicableCompletionEntryDetails.codeActions,
3758
+ exportName
3759
+ );
3622
3760
  if (!result) return applicableCompletionEntryDetails;
3623
3761
  const packagesMetadata = importablePackagesMetadataCache.get(sourceFile.fileName) || (yield* makeImportablePackagesMetadata(sourceFile));
3624
3762
  importablePackagesMetadataCache.set(sourceFile.fileName, packagesMetadata);
3625
- const formatContext = ts.formatting.getFormatContext(
3626
- formatOptions || {},
3627
- languageServiceHost
3628
- );
3629
3763
  const isExcluded = packagesMetadata.isExcludedFromNamespaceImport(
3630
3764
  fileName,
3631
3765
  exportName
3632
3766
  );
3633
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
+ }
3634
3784
  const effectUnbarreledModulePath = packagesMetadata.getUnbarreledModulePath(
3635
3785
  fileName,
3636
3786
  exportName
3637
3787
  );
3638
- const effectNamespaceName = packagesMetadata.getImportNamespaceByFileName(
3788
+ const asNamespaceImport = packagesMetadata.getImportNamespaceByFileName(
3639
3789
  effectUnbarreledModulePath || fileName
3640
3790
  );
3641
- if (!effectNamespaceName) return applicableCompletionEntryDetails;
3642
- const newModuleSpecifier = effectUnbarreledModulePath ? getModuleSpecifier(
3643
- program.getCompilerOptions(),
3644
- sourceFile,
3645
- sourceFile.fileName,
3646
- String(effectUnbarreledModulePath || fileName),
3647
- program
3648
- ) : moduleSpecifier;
3649
- const changes = ts.textChanges.ChangeTracker.with(
3650
- {
3651
- formatContext,
3652
- host: languageServiceHost,
3653
- preferences: preferences || {}
3654
- },
3655
- (changeTracker) => {
3656
- ts.insertImports(
3657
- changeTracker,
3658
- sourceFile,
3659
- ts.factory.createImportDeclaration(
3660
- void 0,
3661
- ts.factory.createImportClause(
3662
- false,
3663
- void 0,
3664
- ts.factory.createNamespaceImport(ts.factory.createIdentifier(effectNamespaceName))
3665
- ),
3666
- ts.factory.createStringLiteral(newModuleSpecifier)
3667
- ),
3668
- true,
3669
- preferences || {}
3670
- );
3671
- if (!effectUnbarreledModulePath) {
3672
- changeTracker.insertText(
3673
- sourceFile,
3674
- effectReplaceSpan.start,
3675
- effectNamespaceName + "."
3676
- );
3677
- }
3678
- }
3679
- );
3680
- return {
3681
- ...applicableCompletionEntryDetails,
3682
- codeActions: [
3683
- {
3684
- description: "Import * as " + effectNamespaceName + " from " + newModuleSpecifier,
3685
- changes
3686
- }
3687
- ]
3688
- };
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
+ };
3813
+ }
3814
+ return applicableCompletionEntryDetails;
3689
3815
  }
3690
3816
  );
3691
3817