marcoroth / stimulus-parser Goto Github PK
View Code? Open in Web Editor NEWStatically analyze Stimulus controllers in your project.
Home Page: https://hotwire.io/ecosystem/tooling/stimulus-parser
License: MIT License
Statically analyze Stimulus controllers in your project.
Home Page: https://hotwire.io/ecosystem/tooling/stimulus-parser
License: MIT License
Related #114
stimulus-lsp is complaining about a missing controller. It's not missing though but was imported via importmaps.
We are using Debian Rails which is at 6.1
for the time being. Note that this Rails version isn't deprecated and will continue to receive security fixes even after Rails stops supporting it, unless there is a new stable Debian.
Using definitionsFromContext
from the @hotwired/stimulus-webpack-helpers
package does not mark Stimulus controllers as registered.
import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers';
const application = Application.start(document.documentElement);
// Normally you'd need to add the webpack env types, but since we'll migrate
// away (in Rails 7), and we know the signatures here, it's ignored and casted
// as any instead.
// @ts-ignore
// eslint-disable-next-line no-undef
const context = require.context('../controllers', true, /_controller\.ts$/);
application.load(definitionsFromContext(context));
declare const window: Window & { Stimulus: Application };
window.Stimulus = application;
The result in the Stimulus Controllers view:
I don't know if this is related, but I don't see any autocompletion in .html.erb
files. I would expect them to not show for unregistered controllers, hence me opening this issue.
Trying to circumvent it by using the Register controller definition on the stimulus application
yields the following error message:
An unexpected token error will be raised if a Stimulus controller has a private getter defined, such as:
get #activeItems () {
return this.itemTargets.filter(div => !div.classList.contains('destroying'))
}
Currently this doesn't get parsed as a valid ValueDefinition
, this is valid Stimulus though.
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = {
string: "Number"
}
}
Similar to #12, it makes sense that the Stimulus Parser understands the mixins Stimulus Use provides. Some of the mixins Stimulus Use provides enhance the controller implicitly with properties and callback methods.
It would be neat to know that these methods and properties exist on a controller and where they come from. Kind of like a "This property is provided by Stimulus Use" message.
I was playing around with the LSP and noticed errors for some of my Stimulus controllers.
Debugging a bit I noticed that the parser fails on them, because we use some empty public class fields like so:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
someField;
// this is just an example how we use them later, not related to the bug
doSomething() {
this.someField = "some value";
}
}
someField = "initial value"
.static
in front of it also breaksIt fails on this line:
Line 45 in 31842f4
I guess those nodes don't have a type
, so some other parsing exception should be added.
Happy to help, but I don't have a good idea in what direction to go.
edit later that day: I made a PR that implements a possible fix in #21!
For example, when we have a controller at:
app/javascript/controllers/example_controller.js
But we decide to import that controller as something-else
:
import { Application } from "@hotwired/stimulus"
import ExampleController from "../controllers/example_controller.js"
const application = Application.start()
application.register("something-else", ExampleController)
We should add a hasExplicitDefaultValue
property to the ValueDefinition
that works according to the use-cases shown below:
static values = {
name: String
}
name: {
type: "String",
defaultValue: "",
hasExplicitDefaultValue: false,
kind: "shorthand",
keyLoc: { ... },
valueLoc: { ... },
}
static values = {
name: { type: String },
}
name: {
type: "String",
defaultValue: "",
hasExplicitDefaultValue: false,
kind: "explicit",
keyLoc: { ... },
valueLoc: { ... },
}
static values = {
name: { type: String, default: "Bob" },
}
name: {
type: "String",
defaultValue: "Bob",
hasExplicitDefaultValue: true,
kind: "explicit",
keyLoc: { ... },
valueLoc: { ... },
}
static values = {
name: "Bob"
}
name: {
type: "String",
defaultValue: "Bob",
hasExplicitDefaultValue: true,
kind: "inferred",
keyLoc: { ... },
valueLoc: { ... },
}
@Value(String) nameValue!: string;
name: {
type: "String",
defaultValue: "",
hasExplicitDefaultValue: false,
kind: "decorator",
keyLoc: { ... },
valueLoc: { ... },
}
@Value(String) nameValue! = "Bob"
name: {
type: "String",
defaultValue: "Bob",
hasExplicitDefaultValue: true,
kind: "decorator",
keyLoc: { ... },
valueLoc: { ... },
}
Ideally we have a node/location for each value in the value definition, while also having the node/location for the property-key and property-value.
Currently we get the range for the whole value of everything after static values =
stimulus-decorators is a great project bringing @github/catalyst typesafe syntax to stimulus
It would be great if we could also parse those libraries but make sure you can mix and match (i.e. static values
but decorated @Targets
)
Shorthand-version:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = {
name: String
}
}
Explicit-version:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = {
name: {
type: String,
default: "Stimulus"
}
}
}
Inferred-version:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = {
name: "Stimulus"
}
}
We currently assume that the Stimulus controller files are named with underscores.
stimulus-parser/src/project.ts
Line 83 in c0fab10
We should also support dasherized file names or files that don't include the _controller
suffix.
An example might be something like tailwindcss-stimulus-components
, where you import and register controllers like:
import { Dropdown, Modal } from "tailwindcss-stimulus-components"
application.register('dropdown', Dropdown)
application.register('modal', Modal)
We might be able to statically analyze all Application.register("name", Controller)
calls in the app to detect those controllers this way.
Maybe we need to move on from acorn for this, but to enable support for TypeScript controller files in Stimulus LSP we need to be able to parse TypeScript syntax.
It's common for Stimulus controllers to inherit some behaviour from an (abstract) base controller.
Currently the inherited methods
, targets
, values
, etc. aren't provided on the child controller.
Like:
// base_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["base"]
}
// specfic_controller.js
import BaseController from "./base_controller"
export default class extends BaseController {
static targets = ["specific"]
}
The specific
controller should now have both the base
and specific
target.
My project have some external stimulus components specifically from Stimulus Components
Currently the parser doesn't recognize if a file defines more than one controller.
Acorn might not be the best choice here. While slower and harder to reason with, the TypeScript parser might be the easier route to take in order to obtain lexical context of each controller.
Referencing #9
Most people follow the convention of having the controllers in a **/**/controllers/
folder and having the filename end with the _controller.{js,ts}
suffix. Though there are still cases where people don't or cannot follow this convention.
In order to still support that we would need to look into all *.js
or *.ts
in a project and check if they somehow reference Stimulus controllers.
Currently, given this file, the targets ["name", "output"]
will be assigned to both classes
import { Controller } from "@hotwired/stimulus"
class One extends Controller {}
class Two extends Controller {}
One.targets = ["name", "output"]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.