@effect/language-service 0.22.3 → 0.23.1

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
@@ -6,7 +6,7 @@ This package implements a TypeScript language service plugin that allows additio
6
6
 
7
7
  1. `npm install @effect/language-service --save-dev` in your project
8
8
  2. inside your tsconfig.json, you should add the plugin configuration as follows:
9
- ```json
9
+ ```jsonc
10
10
  {
11
11
  "compilerOptions": {
12
12
  "plugins": [
@@ -72,7 +72,7 @@ And you're done! You'll now be able to use a set of refactor and diagnostics tha
72
72
 
73
73
  Few options can be provided alongside the initialization of the Language Service Plugin.
74
74
 
75
- ```json
75
+ ```jsonc
76
76
  {
77
77
  "compilerOptions": {
78
78
  "plugins": [
@@ -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
  }
@@ -99,7 +101,7 @@ TypeScript LSP are loaded only while editing your files. That means that if you
99
101
  HOWEVER, if you use `ts-patch` you can enable the transform as well to get the diagnostics also at compile time.
100
102
  Your `tsconfig.json` should look like this:
101
103
 
102
- ```json
104
+ ```jsonc
103
105
  {
104
106
  "compilerOptions": {
105
107
  "plugins": [
@@ -141,7 +143,7 @@ Effect.succeed(1); // This will be reported as a floating effect
141
143
 
142
144
  or you can set the severity for the entire project in the global plugin configuration
143
145
 
144
- ```json
146
+ ```jsonc
145
147
  {
146
148
  "compilerOptions": {
147
149
  "plugins": [
@@ -167,7 +169,7 @@ If you did not installed the Svelte LSP into your local project but instead thro
167
169
 
168
170
  Your tsconfig should look like this:
169
171
 
170
- ```json
172
+ ```jsonc
171
173
  {
172
174
  "compilerOptions": {
173
175
  "plugins": [
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,164 @@ 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
+ const existingImportSpecifier = namedImports.elements.find(
3690
+ (element) => element.name.text.toLowerCase() === barrelExportName.toLowerCase()
3691
+ );
3692
+ if (existingImportSpecifier) {
3693
+ foundImportDeclaration = true;
3694
+ break;
3695
+ }
3696
+ changeTracker.replaceNode(
3697
+ sourceFile,
3698
+ namedImports,
3699
+ ts.factory.createNamedImports(
3700
+ namedImports.elements.concat([
3701
+ ts.factory.createImportSpecifier(false, void 0, ts.factory.createIdentifier(barrelExportName))
3702
+ ])
3703
+ )
3704
+ );
3705
+ foundImportDeclaration = true;
3706
+ break;
3707
+ }
3605
3708
  }
3606
3709
  }
3607
3710
  }
3608
- );
3711
+ if (!foundImportDeclaration) {
3712
+ ts.insertImports(
3713
+ changeTracker,
3714
+ sourceFile,
3715
+ ts.factory.createImportDeclaration(
3716
+ void 0,
3717
+ ts.factory.createImportClause(
3718
+ false,
3719
+ void 0,
3720
+ ts.factory.createNamedImports(
3721
+ [
3722
+ ts.factory.createImportSpecifier(false, void 0, ts.factory.createIdentifier(barrelExportName))
3723
+ ]
3724
+ )
3725
+ ),
3726
+ ts.factory.createStringLiteral(newModuleSpecifier)
3727
+ ),
3728
+ true,
3729
+ preferences || {}
3730
+ );
3731
+ }
3732
+ changeTracker.insertText(
3733
+ sourceFile,
3734
+ effectReplaceSpan.start,
3735
+ barrelExportName + "."
3736
+ );
3737
+ }
3738
+ );
3739
+ return [
3740
+ {
3741
+ description: "Import { " + barrelExportName + " } from " + newModuleSpecifier,
3742
+ changes
3743
+ }
3744
+ ];
3745
+ });
3746
+ var postprocessCompletionEntryDetails = fn("postprocessCompletionEntryDetails")(
3747
+ function* (sourceFile, data, applicableCompletionEntryDetails, formatOptions, preferences, languageServiceHost) {
3748
+ const languageServicePluginOptions = yield* service(LanguageServicePluginOptions);
3749
+ if (languageServicePluginOptions.namespaceImportPackages.length === 0 && languageServicePluginOptions.barrelImportPackages.length === 0) return applicableCompletionEntryDetails;
3609
3750
  const ts = yield* service(TypeScriptApi);
3610
3751
  const program = yield* service(TypeScriptProgram);
3611
3752
  const getModuleSpecifier = makeGetModuleSpecifier(ts);
@@ -3618,74 +3759,66 @@ var postprocessCompletionEntryDetails = fn("postprocessCompletionEntryDetails")(
3618
3759
  if (!moduleSpecifier) return applicableCompletionEntryDetails;
3619
3760
  if (!("effectReplaceSpan" in data)) return applicableCompletionEntryDetails;
3620
3761
  const effectReplaceSpan = data.effectReplaceSpan;
3621
- const result = yield* isAutoImportOnlyCodeActions(applicableCompletionEntryDetails.codeActions, exportName);
3762
+ const result = yield* isAutoImportOnlyCodeActions(
3763
+ sourceFile,
3764
+ applicableCompletionEntryDetails.codeActions,
3765
+ exportName
3766
+ );
3622
3767
  if (!result) return applicableCompletionEntryDetails;
3623
3768
  const packagesMetadata = importablePackagesMetadataCache.get(sourceFile.fileName) || (yield* makeImportablePackagesMetadata(sourceFile));
3624
3769
  importablePackagesMetadataCache.set(sourceFile.fileName, packagesMetadata);
3625
- const formatContext = ts.formatting.getFormatContext(
3626
- formatOptions || {},
3627
- languageServiceHost
3628
- );
3629
3770
  const isExcluded = packagesMetadata.isExcludedFromNamespaceImport(
3630
3771
  fileName,
3631
3772
  exportName
3632
3773
  );
3633
3774
  if (isExcluded) return applicableCompletionEntryDetails;
3775
+ const asBarrelImport = packagesMetadata.getBarreledModulePath(fileName);
3776
+ if (asBarrelImport) {
3777
+ const codeActions = yield* getImportFromBarrelCodeActions(
3778
+ formatOptions,
3779
+ preferences,
3780
+ languageServiceHost,
3781
+ sourceFile,
3782
+ effectReplaceSpan,
3783
+ asBarrelImport.packageName,
3784
+ asBarrelImport.exportName
3785
+ );
3786
+ return {
3787
+ ...applicableCompletionEntryDetails,
3788
+ codeActions
3789
+ };
3790
+ }
3634
3791
  const effectUnbarreledModulePath = packagesMetadata.getUnbarreledModulePath(
3635
3792
  fileName,
3636
3793
  exportName
3637
3794
  );
3638
- const effectNamespaceName = packagesMetadata.getImportNamespaceByFileName(
3795
+ const asNamespaceImport = packagesMetadata.getImportNamespaceByFileName(
3639
3796
  effectUnbarreledModulePath || fileName
3640
3797
  );
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
- };
3798
+ if (asNamespaceImport) {
3799
+ const newModuleSpecifier = effectUnbarreledModulePath ? getModuleSpecifier(
3800
+ program.getCompilerOptions(),
3801
+ sourceFile,
3802
+ sourceFile.fileName,
3803
+ String(effectUnbarreledModulePath || fileName),
3804
+ program
3805
+ ) : moduleSpecifier;
3806
+ const codeActions = yield* getImportFromNamespaceCodeActions(
3807
+ formatOptions,
3808
+ preferences,
3809
+ languageServiceHost,
3810
+ sourceFile,
3811
+ effectReplaceSpan,
3812
+ asNamespaceImport,
3813
+ effectUnbarreledModulePath,
3814
+ newModuleSpecifier
3815
+ );
3816
+ return {
3817
+ ...applicableCompletionEntryDetails,
3818
+ codeActions
3819
+ };
3820
+ }
3821
+ return applicableCompletionEntryDetails;
3689
3822
  }
3690
3823
  );
3691
3824