Giter Site home page Giter Site logo

marcoroth / stimulus-lsp Goto Github PK

View Code? Open in Web Editor NEW
170.0 4.0 10.0 2.4 MB

Intelligent Stimulus tooling for Visual Studio Code and Neovim

Home Page: https://hotwire.io/ecosystem/tooling/stimulus-lsp

License: MIT License

TypeScript 99.01% Shell 0.19% JavaScript 0.80%
hotwire language-server language-server-protocol lsp stimulus stimulusjs hacktoberfest

stimulus-lsp's Introduction

Stimulus LSP

Intelligent Stimulus tooling for Visual Studio Code

Functionality

Currently, this Language Server only works for HTML, though its utility extends to various file types such as ERB, PHP, or Blade files.

Completions

  • Data Attributes
  • Completions for controller identifiers
  • Completions for controller actions
  • Completions for controller targets
  • Completions for controller values
  • Completions for controller classes

Diagnostics

HTML Files

  • Missing controllers (stimulus.controller.invalid)
  • Missing controller actions (stimulus.action.invalid)
  • Missing controller targets (stimulus.controller.target.missing)
  • Missing controller values (stimulus.controller.value.missing)
  • Invalid action descriptors (stimulus.action.invalid)
  • Data attributes format mismatches (stimulus.attribute.mismatch)
  • Controller values type mismatches (stimulus.controller.value.type_mismatch)

JavaScript Files/Stimulus Controller Files

  • Controller value definition default value type mismatch (stimulus.controller.value_definition.default_value.type_mismatch)
  • Unknown value definition type (stimulus.controller.value_definition.unknown_type)
  • Controller parsing errors (stimulus.controller.parse_error)
  • Import from deprecated packages (stimulus.package.deprecated.import)

Quick-Fixes

  • Create a controller with the given identifier (stimulus.controller.create)
  • Update controller identifier with did you mean suggestion (stimulus.controller.update)
  • Register a controller definition from your project or a NPM package (stimulus.controller.register)
  • Update controller action name with did you mean suggestion (stimulus.controller.action.update)
  • Implement a missing controller action on controller (stimulus.controller.action.implement)

Structure

.
├── package.json // The extension manifest.
|
├── client // Language Client
│   └── src
│      └── extension.ts // Language Client entry point
|
└── server // Language Server
    └── src
        └── server.ts // Language Server entry point

Running the extension locally

  • Run yarn install in this folder. This installs all necessary npm modules in both the client and server folder
  • Open VS Code on this folder.
  • Press Ctrl+Shift+B to compile the client and server.
  • Switch to the Debug viewlet.
  • Select Launch Client from the drop down.
  • Run the launch config.
  • If you want to debug the server as well use the launch configuration Attach to Server
  • In the [Extension Development Host] instance of VSCode, open a HTML file.
    • Type <div data-controller="|">, place your cursor where the | is, hit Ctrl+Space and you should see completions.

Install instructions: Neovim

Install instructions can be found at nvim-lspconfig

stimulus-lsp's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

stimulus-lsp's Issues

stimulus-language-server package does not run

Hi, first of all thanks for this awesome LSP server.

I followed the instructions in: https://www.npmjs.com/package/stimulus-language-server. However, I am getting the following errors when I run stimulus-language-server --stdio.

/usr/local/bin/stimulus-language-server: line 1: use strict: command not found
/usr/local/bin/stimulus-language-server: line 2: syntax error near unexpected token `exports,'
/usr/local/bin/stimulus-language-server: line 2: `Object.defineProperty(exports, "__esModule", { value: true });'

After some searching I found that in Unix-systems, one has to add a shebang, else the script gets run as a shell script. Indeed, when I add the following line on top of the file in /usr/local/bin/, it works.

#!/usr/bin/env node

Diagnostic: Warn if `removeEventListener` is not called in `disconnect()`

When writing Stimulus controllers you might find yourself adding and removing event listeners in the connect and disconnect lifecycle methods.

We should add a diagnostic to the controller, if the event listener gets added but not removed in disconnect.

Don't:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    document.addEventListener("event", () => { })
  }
}

Do:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    document.addEventListener("event", () => { })
  }

  disconnect() {
    document.removeEventListener("event", () => { })
  }
}

or, alternatively:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  initialize() {
    // So the event listener only gets added once
    document.addEventListener("event", () => { })
  }
}

False positive: type mismatch?

image

export default class extends Controller {
  static targets = ['container']
  static values = {
    selector: String,
    frameId: String,
    url: String,
    enabled: Boolean,
    border: Boolean,
  }

Is there some better way to write this so that the LSP will know that the ERB string interpolation is going to return a boolean? Otherwise, seems like this type mismatch is almost also going to fall for non String types?

Bug: values with dash not working?

image

export default class extends Controller {
  static targets = ['container']
  static values = {
    selector: String,
    frameId: String,
    url: String,
    enabled: Boolean,
    border: Boolean,
  }

Looks like frameId -> frame-id value is not getting parsed correctly?

Support for TypeScript controller files

Currently Stimulus controller files ending in .ts are not supported yet, because the stimulus-parser is not ready to parse TypeScript syntax just yet.

As soon as the stimulus-parser can properly parse TypeScript files we can also add support for TypeScript Stimulus controllers

Bring back automated E2E tests

I stripped out the tests in this commit: 4687b4b

We should bring them back so we can run the automated tests in CI to make sure we don't introduce any regressions, especially as we are covering more and more advanced use-cases and edge cases.

ERB support

Provide support/deep-integration for ERB templates.

Ideally it would also support Rails specific helpers like content_tag or tag.[tag] and provide autocomplete in the data hash.

Haml Support

People have been asking for Haml support.

I personally don't have a lot of motivation myself to push for this, but that doesn't mean that we can't or shouldn't support it. Please let me know if anyone wants to help out with this so that we can probably support Haml as well.

Support controllers which were registered with a different name than what the filename says

A controller like app/javascript/controllers/hello_controller.js would currently be provided as hello.

But there are cases where one could register a controller with another identifier, like:

import HelloController from "./controllers/hello_controller.js"

application.register("something-else", HelloController)

Where the provided hello identifier wouldn't be right anymore, instead it should be provided as something-else.

Support for custom JS namespaces

Description

My app has multiple namespaces: admin and front_end. These are divided in 2 entrypoints that import their own controllers, as well as a set of shared controllers:

Entrypoints

  • app/javascript/admin.js
import "./shared";
import "./admin/controllers";
  • app/javascript/front_end.js
import "./shared";
import "./front_end/controllers";

Admin controllers

  • app/javascript/admin/controllers/index.js
import { application } from "../../shared/controllers/application";

import PrintersController from "./printers_controller";
application.register("printers", PrintersController);

Front end controllers

  • app/javascript/front_end/controllers/index.js
import { application } from "../../shared/controllers/application";

import PaymentController from "./payment_controller";
application.register("payment", PaymentController);

Shared index

  • app/javascript/shared/index.js
import "@hotwired/turbo-rails";
import "./controllers";

import { application } from "./controllers/application";

// https://www.stimulus-components.com/docs/stimulus-reveal-controller/
import Reveal from "stimulus-reveal-controller";
application.register("reveal", Reveal);

Shared application

  • app/javascript/shared/controllers/application.js
import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Configure Stimulus development experience
application.debug = false
window.Stimulus   = application

export { application }

Shared controllers

  • app/javascript/shared/controllers/index.js
import { application } from "./application";

import ModalController from "./modal_controller";
application.register("modal", ModalController);

Admin layout

<%= javascript_include_tag "admin", "data-turbo-track": "reload", defer: true %>

Front end layout

<%= javascript_include_tag "front_end", "data-turbo-track": "reload", defer: true %>

When on a .html.erb file, Stimulus LSP is referencing the wrong path, e.g.

On views/admin/orders/index.html.erb

<div 
  data-controller="modal"
  data-action="click->modal#show"
></div>

Stimulus LSP would complain about the data-action attribute because "modal isn't a valid Stimulus controller. Did you mean app--javascript--shared-controllers--modal?"

Versions

  • Stimulus LSP v.0.1.1
  • stimulus-rails 1.3.0

Notes

Let me know if you'd like a new Rails app reproducing this or if the information above is enough to drive the discussion forward.

Document / Fix Language and File Naming Constraints

First of all, thanks for the amazing work you're doing here @marcoroth.

When I started using this extension, I immediately started getting false negatives for stimulus.controller.invalid.

After some digging into the code, and doing some experimentation, I came to the conclusion that this extension currently has a couple of constraints, which don't seem to be documented on the VSCode extension itself:

  1. The extension currently doesn't support Typescript files (they simply don't get recognized?)
  2. The extension expects all controller files to be named ${some_name}_controller (which I guess makes sense, in that it mirrors the Rails conventions).

Going a bit through the code, it seems to me that the main source of these limitations is this line on your Stimulus Parser library.

If my diagnostic here is correct, then I think newcomers would greatly benefit by having these two constraints clearly documented at a top level.

Obviously, it'd be even better if we can get rid of these constraints.

Quick Fix Action to apply "Did you mean" suggestion

Currently if you use a controller identifier Stimulus LSP doesn't know about it only shows you the error with a "Did you mean" suggestion:

CleanShot 2023-10-19 at 03 50 30

It also offers a Quick Fix Action to create a Stimulus controller with the identifier it didn't know about:
CleanShot 2023-10-19 at 03 50 51

In this case we could also offer a Quick Fix Action which replaces the unknown identifier with the value from the "Did you mean" suggestion.

Handle syntax errors when parsing controllers

If the controllers in the users codebase have syntax errors they are currently just getting ignored.

We should add a diagnostic message to all files that couldn't be parsed by stimulus-parser

Slim support

Although “slim” is mentioned in the code, it doesn't seem to work. Is it a bug or a missing feature ?

Diagnostics for Stimulus Value data attributes hierarchy

Stimulus Values data attributes need to be placed on the same level where the corresponding data-controller is placed.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = { open: Boolean }
}

So we could provide diagnostics for situations like:

<div data-controller="dropdown">
  <div data-dropdown-open-value="true"></div>
  <!-- ^^^^^^^^^^^^^^^^^^^^^^^^ The Stimulus Value `open` for the `dropdown` controller needs to be placed on the same element as the `data-controller="dropdown"` element -->
</div>

Diagnostic: Warn if controller extends `Controller` from `stimulus` package

We should add a diagnostic to "warn" people that they should migrate the the "new" package in the @hotwired namespace.

Bad:

import { Controller } from "stimulus"

export default class extends Controller { 
  // ...
}

Good:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller { 
  // ...
}

Bad:

import { Application } from "stimulus"

const application = Application.start()

Good:

import { Application } from "@hotwired/stimulus"

const application = Application.start()

Diagnostic: Make sure to call `super` in extended controller

// application_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  something() {
    console.log("something in application controller")
  }
}
// some_controller.js

import ApplicationController from "./application_controller"

export default class extends ApplicationController {
  something() {
    // If the something() action exists in this and the extended parent controller 
    // we want to add a diagnostic if the current controller doesn't call super
    super.something() 

    console.log("something in some controller")
  }
}

Code Action: Rename Stimulus Controller

It would be cool if you could refactor/rename a Stimulus controller and it will rename the:

  • controller file name
  • all references in data-controller attributes
  • all data-[identifier]-(target/value/class) attributes
  • (optional) all references in ERB data: { [identifier]_(target/value/class): "..." } attributes

Support for Stimulus Use

The mixins Stimulus Use provides add callbacks and other properties to the controller classes. The Stimulus LSP could enhance them by providing inline-documentation or might be able to provide other helpful diagnostics for it.

To enable this, the Stimulus Parser needs to support parsing Stimulus Use mixins: marcoroth/stimulus-parser#13

Diagnostic: Warn if `Application.register()` gets called with "invalid" controller identifier

import { application } from "./application"

application.register("camelCased", controller)
//                   ^^^^^^^^^^^^
//                   Use dasherized instead of camelCased controller identifiers. 
//                   Quick-Fix: Rename to `camel-cased`

Similar with underscored idedentifiers:

import { application } from "./application"

application.register("under_scored", controller)
//                   ^^^^^^^^^^^^^^
//                   Use dasherized instead of under_scored controller identifiers. 
//                   Quick-Fix: Rename to `under-scored`

Inspired by:

https://x.com/george_kettle/status/1721945825968902160

Autocomplete for JavaScript

The Stimulus LSP currently only supports completion for HTML and (HTML+ERB). There's also an opportunity to provide autocomplete for JavaScript files, especially within Stimulus controllers themselves.

For example, a controller like this should provide autocomplete for targets and all other Stimulus APIs:

import  { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["name"]
  
  connect() {
    this.|
         +----------------+
         | nameTarget     |
         | nameTargets    |
         | hasNameTarget  |
         +----------------+
  }
}

Goto Definition is broken

I am using neovim, when I try to "Goto Definition", I get the following error:

Error executing vim.schedule lua callback: ...l/Cellar/neovim/0.9.4/share/nvim/runtime/lua/vim/uri.lua:107: URI must contain a scheme: /usr/local/var/www/sticket/app/javascript/controllers/show_controller.js
stack traceback:
        [C]: in function 'assert'
        ...l/Cellar/neovim/0.9.4/share/nvim/runtime/lua/vim/uri.lua:107: in function 'uri_to_fname'
        ...l/Cellar/neovim/0.9.4/share/nvim/runtime/lua/vim/uri.lua:128: in function 'uri_to_bufnr'
        ...lar/neovim/0.9.4/share/nvim/runtime/lua/vim/lsp/util.lua:1127: in function 'jump_to_location'
        ...neovim/0.9.4/share/nvim/runtime/lua/vim/lsp/handlers.lua:412: in function 'handler'
        ...l/Cellar/neovim/0.9.4/share/nvim/runtime/lua/vim/lsp.lua:1393: in function ''
        vim/_editor.lua: in function <vim/_editor.lua:0>

I think neovim is expecting a URI with a scheme, e.g. file://usr/local/var/www/sticket/app/javascript/controllers/show_controller.js instead of /usr/local/var/www/sticket/app/javascript/controllers/show_controller.js

Diagnostic: Make sure that Stimulus Controllers are exported

We should add a warning if you define/extend the Stimulus Controller class in a file and you are not exporting it.

Bad:

import { Controller } from "@hotwired/stimulus"

class HelloController extends Controller {

}

Good:

import { Controller } from "@hotwired/stimulus"

export default class HelloController extends Controller {

}

or:

import { Controller } from "@hotwired/stimulus"

class HelloController extends Controller {

}

export default HelloController

Diagnostics for Stimulus Target Callbacks

Whenever a Stimulus Target connects it invokes callbacks on the controller instance.

A controller with a target name invokes the nameTargetConnected callback whenever a name target connects:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["name"]

  nameTargetConnected(target) {
    console.log("name target connected")
  }
}

We can provide diagnostics for cases like:

  • a target callback references an undefined/unknown target on the controller

    import { Controller } from "@hotwired/stimulus"
    
    export default class extends Controller {
      static targets = ["name"]
    
      namTargetConnected(target) {
    //^^^^^^^^^^^^^^^^^^ - `nam` references an unknown Stimulus Target. Did you mean "name"?
      }
    }

More explicit behavior if parsing controller fails

I found a bug in the parser where it can't properly parse some of my controllers (issue open for that: marcoroth/stimulus-parser#20). It took me a while to figure out what was going on, because the LSP is giving a ton of false positives and it wasn't clear to me that it actually couldn't parse the code until I pulled out the code and ran a debugger on it.

Because it can't parse the file, it doesn't register any actions, targets or values. It then starts giving you "xxx" isn't a valid Stimulus Action/Target/Value on the "blabla" controller everywhere.

This is not super friendly, because it gives a false idea that something's wrong with your code.

Possible solutions:

  • Not give any diagnostics (is that right term?) when it can't parse the controller. I think the big down side of this is that you then get a false sense of safety (no warnings, so nothing wrong).
  • Give a generic error diagnostic that it can't parse the controller. Maybe they can be pointed here to help fix a bug etc. At least you then have the idea that the LSP is working.

Also happy to contribute a bit here, but not sure in what direction to take it!

Diagnostic: Prefer arrow-functions for callbacks over `.bind(this)`

Don't:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    document.addEventListener("event", this.callback.bind(this))
  }

  callback() {
    console.log(this)  
  }

  disconnect() {
    document.removeEventListener("event", this.callback.bind(this))
  }
}

Do:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    document.addEventListener("event", this.callback)
  }

  callback = () => {
    console.log(this)  
  }

  disconnect() {
    document.removeEventListener("event", this.callback)
  }
}

Support for Phlex components

Opening this issue to explore support for Phlex components, which are .rb files. Haven't dug into the code, but wonder if it's as simple as expanding the watching folders or allowing these to be configured?

Note that this would mean that data attributes using underscore would need to be supported, e.g.:

div data_my_controller_target: :test do
  # ...
end

Support for Interpolated Controller Names

I have the following code in one of my views:

<div data-controller="<%= stimulus_controller %>">
  <%# ... %>
</div>

Which creates an error with the diagnostic code stimulus.controller.invalid

"stimulus_controller" isn't a valid Stimulus controller. Did you mean "app--javascript--shared--table"?Stimulus LSP(stimulus.controller.invalid)

My expectation in this situation would be that the LSP ignores interpolated ruby code whether for controllers, values, actions, etc. rather than being an invalid controller name.

I have a little experience with VS Code extensions so would be happy to pair on this if you let me know!

Thanks for all you're doing with this!

Diagnostic: Prefer `this.dispatch` over `this.element.dispatchEvent`

Over the years I've seen a bunch of Stimulus controllers doing something along the lines of:

this.element.dispatchEvent(new window.CustomEvent('change', { bubbles: true }))

Stimulus now ships a helper which allows to dispatch events on the controller element like:

this.dispatch("change")

It might also make sense to extract this into a more general Stimulus linter that the Stimulus LSP can rely on for providing the diagnostic.

Reference: https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.