Coder Social home page Coder Social logo

binaryage / cljs-oops Goto Github PK

View Code? Open in Web Editor NEW
350.0 350.0 13.0 743 KB

ClojureScript macros for convenient native Javascript object access.

License: Other

Clojure 63.32% Shell 2.83% JavaScript 33.61% HTML 0.25%
clojurescript externs interop

cljs-oops's People

Contributors

adamfrey avatar cloojure avatar darwin avatar dijonkitchen avatar jmlsf avatar jrheard avatar

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  avatar  avatar  avatar

cljs-oops's Issues

oset being strict

Hello,

I'm trying to use oops to interop with Automerge.

This snippet fails to make an update:

(oset+ doc "todos" (str idx) "done" true)

The browser complaints that: data property descriptor has writable=false

But if I try something a bit different:

(gobj/set (oget+ doc "todos" (str idx)) "done" done)

This works just fine, the google impl is a single obj[key] = value. I wonder if there is a way to make oops work directly without having to fall back into Closure helpers.

Warnings with :checked-arrays :warn

Compiling an app that uses cljs-oops with the latest ClojureScript (1.9.946) with :checked-arrays :warn compiler option, the following warnings are printed:

WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [nil string] instead (consider goog.object/get for object access) at line 10 resources/public/js/build/oops/helpers.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [nil string] instead (consider goog.object/get for object access) at line 20 resources/public/js/build/oops/helpers.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [nil string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [js string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [nil string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 28 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [nil string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [js string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [nil string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs
WARNING: cljs.core/aget, arguments must be an array followed by numeric indices, got [any string] instead (consider goog.object/get for object access) at line 31 resources/public/js/build/oops/core.cljs

Would be nice not to see those warnings :)

Don't warn in `oset!` if var is `:const`

In ClojureScript 1.9.655, :const variables are inlined. See https://blog.fikesfarm.com/posts/2017-06-28-clojurescript-const-var-inlining.html and https://dev.clojure.org/jira/browse/CLJS-2093.

Since the variable value is inlined, it should be usable by cljs-oops:

(defonce ^:const ks [:oops :i :did :it :again])
(defonce ^:const strs (mapv #(str \! (name %)) ks))
;["!oops" "!i" "!did" "!it" "!again"]

(oset! #js {} strs true)
;Error: Oops, Unexpected dynamic selector usage (consider using oset!+)

Using [binaryage/oops "0.6.4"] running in browser tests via [thheller/shadow-cljs "2.8.35"] (https://shadow-cljs.github.io/docs/UsersGuide.html#target-browser-test)

cljs-oops with methods that take arguments

What would be the cljs-oops equivalent of:

a.e.f(x).g(y,z);

Is there a better way than:

(ocall (ocall a "e.f" x) "g" y z)

It would be nice to be able to do something similar to Clojure's .. macro, but that doesn't seem to be compatible with cljs-oops's flexible way of handling paths.

Just brainstorming here, maybe it would be possible to support the % character as a placeholder for args, like so:

(ocall a "e.f(%).g" x y z)

Make it clear that there is no need to use cljs-oops with goog libraries

Forgive me if I'm wrong, but using goog libraries in CLJS code is still a JS interop, and technically falls under cljs-oops intentions.
At this point, from the README it's not clear that one must not use cljs-oops with goog libraries as they're included in Google Closure compiler set and are minimized along with your CLJS code.

Escaping special symbols

I'm using SheetJS in some project and I need to retrieve data from obj["!ref"]. Doing (oget obj "!ref") obviously warns me about punching and still generates code about it. Right now I've reverted to using aget, but still it would be nice to handle that case. :)

P.S. Hi! :)

Literal dot in key

I'm dealing with an api that faux namespace their keys with a dot as the namespace separator; example: {"home-page.main-image": {...}}. cljs-oops thinks dots are nested objects. Is there a workaround for this, or should I just use goog.object with this api?

purpose of ocall! and oapply!

I was reading the docs and didn't get any good examples of using ocall! and oapply!

How are they different from the normal ocall and oapply?

`oget` fails if the JS object has `:constructor` set to `null`

=> (oget #js {:constructor nil, :a 1} :a)
TypeError: Cannot read properties of null (reading 'prototype')
    at Object.oops$helpers$is_prototype_QMARK_ [as is_prototype_QMARK_] (helpers.cljs:7)
    at Object.oops$helpers$cljs_type_QMARK_ [as cljs_type_QMARK_] (helpers.cljs:19)
    at Object.oops$core$validate_object_access_dynamically [as validate_object_access_dynamically] (core.cljs:39)
    at eval (views.cljs:308)

The culprit seems to be this function:

(defn is-prototype? [o]
  (identical? (.-prototype (.-constructor o)) o))

Perhaps it should check if (.-constructor o) is not null?

Thanks for Oops!

Hey Binaryage, just want to say thanks for Oops, because man this just saved my butt working on my Cljs-based lockdown project.

I was having a hard time with name-mangling even with :optimizations :simple interfacing with Algolia, and ocall worked straight away. I feel like this should be part of the standard Cljs interop.

Cheers
P.

ocall interaction with *warn-on-infer*

Is it expected that ocall should generate a warning when *warn-on-infer* is enabled?

(ns scratch.core
  (:require [oops.core :refer [ocall]]))

(set! *warn-on-infer* true)

(ocall js/document :getElementById "foo")

Produces compiler warning:

  6  (ocall js/document :getElementById "foo")
     ^--- Cannot infer target type in expression (. fn-126930 call (oops.helpers/unchecked-aget call-info-126931 0) "foo")

Perhaps the oops codegen could typehint the fn-* symbol with ^js/Function?

Runtime error - Resource: oops/core.cljs:42:3 - Use of undeclared Var cljs.core/js-fn?

 Resource: oops/core.cljs:42:3
--------------------------------------------------------------------------------
  39 |   (runtime/validate-object-access-dynamically obj mode key push? check-key-read? check-key-write?))
  40 |
  41 | (defn ^boolean validate-fn-call-dynamically [fn mode]
  42 |   (runtime/validate-fn-call-dynamically fn mode))
---------^----------------------------------------------------------------------
 Use of undeclared Var cljs.core/js-fn?
--------------------------------------------------------------------------------
  43 |
  44 | (defn ^:dynamic punch-key-dynamically! [obj key]
  45 |   (runtime/punch-key-dynamically obj key))
  46 |
--------------------------------------------------------------------------------

Only solution is to go back to version 0.7.0.

[self-host] No such namespace env-config

Hello Darwin!

I was trying to make cljs-oops work in lumo (self-host cljs) and I have run across the following:

#error {:message No such namespace: env-config.core, could not locate env_config/core.cljs, env_config/core.cljc, or Closure namespace "env-config.core" in file oops/config.clj, :data {:tag :cljs/analysis-error}}

I have two questions:

  1. why is the library including a namespace for fetching environment variables? Is it a feature (in which case I can try to document it in the README) ?
  2. Probably the macros are not self-host compatible, would you be open to some sort of refactoring patch that will fix this?

Thanks, as usual awesome work (I have used this successfully with cljs on jvm) !

Add docs for how to use ocall.

Right now there doesn't seem to be any docs on how to use ocall...and I can't seem to figure it out by trial and error myself.

I'm trying to translate:

(.call (oget % "getDoc") %) to use ocall, with no success. I've tried things such as:

(ocall % "getDoc"), (ocall % "getDoc" %), (ocall % ("getDoc")), (ocall "getDoc" %), etc.

I would imagine the first one is what I should be using. But since there's no documentation I can't tell if its breaking because I'm using it wrong, or because of some other error.

Errors when executed over a prepl connection

So this is a bit of a weird onc since there's hardly anyone (possibly just me?) is using prepl for development right now, if you don't care just yet then that's totally fine! Thought I'd let you know anyway ๐Ÿ˜„

The error is Can't change/establish root binding of: *cljs-warnings* with set, which works fine in a normal REPL. This could be an issue with prepl itself that isn't setting up binding in the right way.

Even if I can't fix it, finding a work around would be great. If I find something I'll put it here, any help would be much appreciated though. This error occurs with any oops operation due to the fact that the setting of *cljs-warnings* is attempted by every macro low down.

I'm not sure what the difference is between a prepl and regular REPL though. You can try it yourself by starting a prepl:

clj -J-Dclojure.server.node="{:port 5556 :accept cljs.server.node/prepl}"

Connecting with something like nc:

nc 127.0.0.1 5556

And executing any oops operation such as:

(oops.core/ocall #js {"a" +} "a" 1 1)

Error thrown when using oops with js/Date objects

When entering the code

(def d (js/Date.))
(oops/ocall d "toISOString")

I am getting the error

Oops, Unexpected object value (date-like) #object[devtools.toolbox.t_devtools$toolbox78167]

What gives?

oapply doesnt work for symbolic arguments

Hey guys,
first of all thanks a lot for this awesome project. I have only heard good things about it so I started using it recently.

However I was faced with an error that I cannot get my head around. I am not sure if this is just not the intended use or if this is a bug in the library, so here it is.

If I have something like (oops/apply+ object f args) oops will complain that args is not a sequence. My guess is that it is trying to check this at compile time, however I only know the args value at runtime, just as with the rest of the arguments ๐Ÿ˜ž

Is this a bug or just not an intended use case?

Can't call methods on strings

This case of (. doesn't work when converted to use ocall:

(.contains "foobar" "foo")

This returns true as expected. When I do this:

(ocall "foobar" "contains" "foo")

I get "Oops, Unexpected object value (string)"

goog.isDateLike is triggered by others' sketchy practices

I've had to stop using cljs-oops for a subset of my uses, specifically https://capacitorjs.com plugins[1], because their top-level plugin modules return a Proxy that behaves somewhat like this (not exactly, but this achieves a similar effect):

const p = new Proxy({}, {get: () => () => true})

That is to say, typeof p.anythingAtAll is 'function'.

Unfortunately, goog.isDateLike only looks for goog.isObject and typeof val.getFullYear == 'function'.

I fully appreciate that this style of duck-typed checking is common in JS, including for promises (as thenables) and similar -- and also that this is more of an upstream quirk than anything else.

That said, given that upstream libraries can't always be changed, I wanted to propose configuration to pick which of the safety checks are run, one at a time (maybe a set like #{:date-like :string-like ...}?)

I find that I rarely trigger the date-like check in ordinary use and disabling it for my own codebase would be helpful for this situation -- on the other hand, I find the other runtime checks valuable and do run into them, so I would love to leave them enabled.

I almost wrote the PR alongside the issue, but I wanted to get your thoughts before doing so, in case you have a preference for how such a thing would work.

Thanks so much for the library -- I like it enough to have wrapped it at https://github.com/tekacs/access in a different syntax, which is primarily how I use it. :)

[1]: such as @capacitor/filesystem, which returns the output of registerPlugin in @capacitor/core

under advanced mode, goog-based implementation has better DCE

Currently oops can generate key get/set code either via aget/aset or goog.object/get/goog.object/set [1].

My compilation output tests under advanced mode showed that DCE is more aggressive with goog.object implementation. Compare [2] and [3].

And I don't understand why. Any insights?

[1]

(defn gen-key-get [obj key]
(case (config/key-get-mode)
:core `(cljs.core/aget ~obj ~key) ; => `(~'js* "(~{}[~{}])" ~obj ~key)
:goog `(goog.object/get ~obj ~key)))
(defn gen-key-set [obj key val]
(case (config/key-set-mode)
:core `(cljs.core/aset ~obj ~key ~val) ; => `(~'js* "(~{}[~{}] = ~{})" ~obj ~key ~val)
:goog `(goog.object/set ~obj ~key ~val)))

[2] https://github.com/binaryage/cljs-oops/blob/master/test/transcripts/expected/basic_oget_core.js
[3] https://github.com/binaryage/cljs-oops/blob/master/test/transcripts/expected/basic_oget_goog.js

How to use cljs-oops to access a constructor?

The following constructor call works great with compilation level "none":

(defonce game (js/Phaser.Game. 1152 648 js/Phaser.AUTO "app" #js{"create" create, "preload" preload}))

I'm trying to convert this, using cljs-oops, so it will work under advanced compilation, for example:

(defonce game (new (oget js/Phaser "Game") 1152 648 (oget js/Phaser "AUTO") "app"
                   #js{"create" create,
                       "preload" preload}))

But this does not properly compile into a constructor call. The output that is produced is below. Notice how the inputs are getting moved outside of the constructor call. How do I do this properly?

if ("undefined" === typeof EK) {
  var EK = (new function() {
    return Phaser.Game;
  })(1152, 648, Phaser.AUTO, "app", {create:OK, preload:FK});
}

Use of undeclared Var cljs.core/js-fn?

Hello,

I'm getting this issue when compiling my clojurescript code with shadow-cljs 2.20.17:

------ WARNING #1 - :undeclared-var --------------------------------------------
 Resource: oops/core.cljs:52:3
 Use of undeclared Var cljs.core/js-fn?
--------------------------------------------------------------------------------

I have tried to import the js-fn? macro using:

(ns my.project
  (:require-macros [cljs.core]))

but I still get the error.

Cannot find clojure.spec

Hello Darwin,
I have a project on clojure 1.8.0 and I receive the following.

clojure.lang.ExceptionInfo: failed compiling file:/home/arichiardi/git/rest-resources-viz/src/web/rest_resource_viz/core.cljs {:file #object[java.io.File 0xadc8c4b "/home/arichiardi/git/rest-resources-viz/src/web/rest_resource_viz/core.cljs"]}
...
Caused by: java.io.FileNotFoundException: Could not locate clojure/spec__init.class or clojure/spec.clj on classpath., compiling:(oops/config.clj:1:1)
...
Caused by: java.io.FileNotFoundException: Could not locate clojure/spec__init.class or clojure/spec.clj on classpath.
        ...
        at clojure.core$require.doInvoke(core.clj:5796)
        at clojure.lang.RestFn.invoke(RestFn.java:512)
->      at oops.config$eval30123$loading__5569__auto____30124.invoke(config.clj:1)**
->      at oops.config$eval30123.invokeStatic(config.clj:1)**
->      at oops.config$eval30123.invoke(config.clj:1)**
        at clojure.lang.Compiler.eval(Compiler.java:6927)
        at clojure.lang.Compiler.eval(Compiler.java:6916)
        at clojure.lang.Compiler.load(Compiler.java:7379)
        ... 117 more

Is there a way to avoid requiring clojure.spec if not present at the moment?

Thanks for this lib, it looks fun!

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.