From 98433fb95422ffcdc8e9ac41ed4052029490f01c Mon Sep 17 00:00:00 2001 From: Chen Bin Date: Fri, 30 Oct 2015 00:51:50 +1100 Subject: [PATCH] `js2-print-json-path' added --- js2-mode.el | 61 +++++++++++++++++++++++++++++++++++++++++++ tests/json-path.el | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 tests/json-path.el diff --git a/js2-mode.el b/js2-mode.el index 0f17c1603..35660719f 100644 --- a/js2-mode.el +++ b/js2-mode.el @@ -10878,6 +10878,67 @@ And, if CHECK-ACTIVATION-P is non-nil, use the value of TOKEN." (js2-check-activation-name s (or token js2-NAME))) name)) +;;; Use AST to extract semantic information + +(defun js2-get-element-index-from-array-node (elem array-node &optional hardcoded-array-index) + "Get index of ELEM from ARRAY-NODE or 0 and return it as string." + (let ((idx 0) elems (rlt hardcoded-array-index)) + (setq elems (js2-array-node-elems array-node)) + (if (and elem (not hardcoded-array-index)) + (setq rlt (catch 'nth-elt + (dolist (x elems) + ;; We know the ELEM does belong to ARRAY-NODE, + (if (eq elem x) (throw 'nth-elt idx)) + (setq idx (1+ idx))) + 0))) + (format "[%s]" rlt))) + +(defun js2-print-json-path (&optional hardcoded-array-index) + "Print the path to the JSON value under point, and save it in the kill ring. +If HARDCODED-ARRAY-INDEX provided, array index in JSON path is replaced with it." + (interactive "P") + (let (previous-node current-node + key-name + rlt) + + ;; The `js2-node-at-point' starts scanning from AST root node. + ;; So there is no way to optimize it. + (setq current-node (js2-node-at-point)) + + (while (not (js2-ast-root-p current-node)) + (cond + ;; JSON property node + ((js2-object-prop-node-p current-node) + (setq key-name (js2-prop-node-name (js2-object-prop-node-left current-node))) + (if rlt (setq rlt (concat "." key-name rlt)) + (setq rlt (concat "." key-name)))) + + ;; Array node + ((or (js2-array-node-p current-node)) + (setq rlt (concat (js2-get-element-index-from-array-node previous-node + current-node + hardcoded-array-index) + rlt))) + + ;; Other nodes are ignored + (t)) + + ;; current node is archived + (setq previous-node current-node) + ;; Get parent node and continue the loop + (setq current-node (js2-node-parent current-node))) + + (cond + (rlt + ;; Clean the final result + (setq rlt (replace-regexp-in-string "^\\." "" rlt)) + (kill-new rlt) + (message "%s => kill-ring" rlt)) + (t + (message "No JSON path found!"))) + + rlt)) + ;;; Indentation support (bouncing) ;; In recent-enough Emacs, we reuse the indentation code from diff --git a/tests/json-path.el b/tests/json-path.el new file mode 100644 index 000000000..70aecefb1 --- /dev/null +++ b/tests/json-path.el @@ -0,0 +1,64 @@ +;;; tests/json-path.el --- Test of using js2-mode AST to print JSON path. + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert) +(require 'js2-mode) + +(ert-deftest js2-json-path-with-actual-array-index () + (with-temp-buffer + (insert "var a = { hello: [1, 2, [1,3,3,4, { world: { hell: { yes: [1,2, 'test'] } } }]] };") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path) "hello[2][4].world.hell.yes[2]")))) + +(ert-deftest js2-json-path-pure-arrays () + (with-temp-buffer + (insert "var a = [5, 1, 4, [ 4, [1, 2, [1, 3.9, 4, [1, 2, 'test',3]]]], 9, 9];") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path) "[3][1][2][3][2]")))) + +(ert-deftest js2-json-path-key-is-numeric () + (with-temp-buffer + (insert "var b = {hello: {3 : {world: {2: 'test'}}}};") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path) "hello.3.world.2")))) + +(ert-deftest js2-json-path-not-found () + (with-temp-buffer + (insert "console.log('test');") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (eq (js2-print-json-path) nil)))) + +(ert-deftest js2-json-path-with-array-index-hardcoded () + (with-temp-buffer + (insert "var a = { hello: [1, 2, [1,3,3,4, { world: { hell: { yes: [1,2, 'test'] } } }]] };") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path 1) "hello[1][1].world.hell.yes[1]")) + (should (string= (js2-print-json-path 0) "hello[0][0].world.hell.yes[0]")))) -- 2.39.2