Merge pull request #2074 from hugeping/printer_tree-segfault

printer_tree: avoid NULL pointer dereference in trp_ext_is_present()
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6bdabce..8ed9695 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -61,12 +61,12 @@
 # set version of the project
 set(LIBYANG_MAJOR_VERSION 2)
 set(LIBYANG_MINOR_VERSION 1)
-set(LIBYANG_MICRO_VERSION 80)
+set(LIBYANG_MICRO_VERSION 101)
 set(LIBYANG_VERSION ${LIBYANG_MAJOR_VERSION}.${LIBYANG_MINOR_VERSION}.${LIBYANG_MICRO_VERSION})
 # set version of the library
 set(LIBYANG_MAJOR_SOVERSION 2)
-set(LIBYANG_MINOR_SOVERSION 36)
-set(LIBYANG_MICRO_SOVERSION 1)
+set(LIBYANG_MINOR_SOVERSION 38)
+set(LIBYANG_MICRO_SOVERSION 4)
 set(LIBYANG_SOVERSION_FULL ${LIBYANG_MAJOR_SOVERSION}.${LIBYANG_MINOR_SOVERSION}.${LIBYANG_MICRO_SOVERSION})
 set(LIBYANG_SOVERSION ${LIBYANG_MAJOR_SOVERSION})
 
diff --git a/CMakeModules/FindPCRE2.cmake b/CMakeModules/FindPCRE2.cmake
index 19af7b7..bb44f78 100644
--- a/CMakeModules/FindPCRE2.cmake
+++ b/CMakeModules/FindPCRE2.cmake
@@ -22,19 +22,24 @@
         ${CMAKE_INSTALL_PREFIX}/include)
 
     # Look for the library.
-    find_library(PCRE2_LIBRARY
-        NAMES
-        libpcre2.a
-        pcre2-8
-        PATHS
-        /usr/lib
-        /usr/lib64
-        /usr/local/lib
-        /usr/local/lib64
-        /opt/local/lib
-        /sw/lib
-        ${CMAKE_LIBRARY_PATH}
-        ${CMAKE_INSTALL_PREFIX}/lib)
+    if (WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+        # For the Debug build, the pcre2 library is called pcre2-8d. The Release build should be pcre2-8.
+        find_library(PCRE2_LIBRARY pcre2-8d)
+    else()
+        find_library(PCRE2_LIBRARY
+            NAMES
+            libpcre2.a
+            pcre2-8
+            PATHS
+            /usr/lib
+            /usr/lib64
+            /usr/local/lib
+            /usr/local/lib64
+            /opt/local/lib
+            /sw/lib
+            ${CMAKE_LIBRARY_PATH}
+            ${CMAKE_INSTALL_PREFIX}/lib)
+    endif()
 
     if(PCRE2_INCLUDE_DIR AND PCRE2_LIBRARY)
         # learn pcre2 version
diff --git a/CMakeModules/UseCompat.cmake b/CMakeModules/UseCompat.cmake
index 34a9741..ef3df89 100644
--- a/CMakeModules/UseCompat.cmake
+++ b/CMakeModules/UseCompat.cmake
@@ -54,7 +54,6 @@
     check_function_exists(timegm HAVE_TIMEGM)
     check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
     check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
-    check_symbol_exists(dirname "libgen.h" HAVE_DIRNAME)
     check_symbol_exists(setenv "stdlib.h" HAVE_SETENV)
 
     unset(CMAKE_REQUIRED_DEFINITIONS)
diff --git a/compat/compat.c b/compat/compat.c
index 5fb2be8..24f235c 100644
--- a/compat/compat.c
+++ b/compat/compat.c
@@ -86,9 +86,9 @@
 ssize_t
 getline(char **lineptr, size_t *n, FILE *stream)
 {
-    static char line[256];
+    static char chunk[256];
     char *ptr;
-    ssize_t len;
+    ssize_t len, written;
 
     if (!lineptr || !n) {
         errno = EINVAL;
@@ -99,28 +99,31 @@
         return -1;
     }
 
-    if (!fgets(line, 256, stream)) {
-        return -1;
-    }
-
-    ptr = strchr(line, '\n');
-    if (ptr) {
-        *ptr = '\0';
-    }
-
-    len = strlen(line);
-
-    if (len + 1 < 256) {
-        ptr = realloc(*lineptr, 256);
-        if (!ptr) {
-            return -1;
+    *n = *lineptr ? *n : 0;
+    written = 0;
+    while (fgets(chunk, sizeof(chunk), stream)) {
+        len = strlen(chunk);
+        if (written + len > *n) {
+            ptr = realloc(*lineptr, *n + sizeof(chunk));
+            if (!ptr) {
+                return -1;
+            }
+            *lineptr = ptr;
+            *n = *n + sizeof(chunk);
         }
-        *lineptr = ptr;
-        *n = 256;
+        memcpy(*lineptr + written, &chunk, len);
+        written += len;
+        if ((*lineptr)[written - 1] == '\n') {
+            break;
+        }
+    }
+    if (written) {
+        (*lineptr)[written] = '\0';
+    } else {
+        written = -1;
     }
 
-    strcpy(*lineptr, line);
-    return len;
+    return written;
 }
 
 #endif
@@ -322,21 +325,6 @@
 #endif
 #endif
 
-#ifndef HAVE_DIRNAME
-#ifdef _WIN32
-#include <shlwapi.h>
-char *
-dirname(char *path)
-{
-    PathRemoveFileSpecA(path);
-    return path;
-}
-
-#else
-#error No dirname() implementation for this platform is available.
-#endif
-#endif
-
 #ifndef HAVE_SETENV
 #ifdef _WIN32
 int
diff --git a/compat/compat.h.in b/compat/compat.h.in
index 0fa2fe7..3baa489 100644
--- a/compat/compat.h.in
+++ b/compat/compat.h.in
@@ -71,7 +71,6 @@
 #cmakedefine HAVE_TIMEGM
 #cmakedefine HAVE_STRPTIME
 #cmakedefine HAVE_MMAP
-#cmakedefine HAVE_DIRNAME
 #cmakedefine HAVE_STRCASECMP
 #cmakedefine HAVE_SETENV
 
diff --git a/compat/posix-shims/strings.h b/compat/posix-shims/strings.h
index c917a66..1ac0519 100644
--- a/compat/posix-shims/strings.h
+++ b/compat/posix-shims/strings.h
@@ -7,3 +7,11 @@
 #error No strcasecmp() implementation for this platform is available.
 #endif
 #endif
+
+#ifndef HAVE_STRNCASECMP
+#ifdef _MSC_VER
+#define strncasecmp _strnicmp
+#else
+#error No strncasecmp() implementation for this platform is available.
+#endif
+#endif
diff --git a/distro/pkg/rpm/libyang.spec b/distro/pkg/rpm/libyang.spec
index eccc4a5..f57ef91 100644
--- a/distro/pkg/rpm/libyang.spec
+++ b/distro/pkg/rpm/libyang.spec
@@ -47,27 +47,46 @@
 
 %prep
 %autosetup -p1
+%if 0%{?rhel} && 0%{?rhel} < 8
+    mkdir build
+%endif
 
 %build
-%cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO
-%cmake_build
-
-%if "x%{?suse_version}" == "x"
-cd %{__cmake_builddir}
+%if 0%{?rhel} && 0%{?rhel} < 8
+  cd build
+  cmake \
+    -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \
+    -DCMAKE_BUILD_TYPE:String="Release" \
+    -DCMAKE_C_FLAGS="${RPM_OPT_FLAGS}" \
+    -DCMAKE_CXX_FLAGS="${RPM_OPT_FLAGS}" \
+    ..
+  make
+%else
+  %cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO
+  %cmake_build
+  %if "x%{?suse_version}" == "x"
+    cd %{__cmake_builddir}
+  %endif
 %endif
 make doc
 
 %check
-%if "x%{?suse_version}" == "x"
-cd %{__cmake_builddir}
+%if ( 0%{?rhel} == 0 ) || 0%{?rhel} > 7
+  %if "x%{?suse_version}" == "x"
+    cd %{__cmake_builddir}
+  %endif
 %endif
 ctest --output-on-failure -V %{?_smp_mflags}
 
 %install
-%cmake_install
-
 mkdir -m0755 -p %{buildroot}/%{_docdir}/libyang
-cp -a doc/html %{buildroot}/%{_docdir}/libyang/html
+%if 0%{?rhel} && 0%{?rhel} < 8
+  cd build
+  make DESTDIR=%{buildroot} install
+%else
+  %cmake_install
+  cp -a doc/html %{buildroot}/%{_docdir}/libyang/html
+%endif
 
 %files
 %license LICENSE
diff --git a/src/common.c b/src/common.c
index 38f51ea..f3b1d6b 100644
--- a/src/common.c
+++ b/src/common.c
@@ -258,6 +258,7 @@
                 (value != 0x09) &&
                 (value != 0x0a) &&
                 (value != 0x0d)) {
+            /* valid UTF8 but not YANG string character */
             return LY_EINVAL;
         }
 
diff --git a/src/context.h b/src/context.h
index 10bbef4..a6367f5 100644
--- a/src/context.h
+++ b/src/context.h
@@ -197,6 +197,8 @@
 #define LY_CTX_ENABLE_IMP_FEATURES 0x0100 /**< By default, all features of newly implemented imported modules of
                                         a module that is being loaded are disabled. With this flag they all become
                                         enabled. */
+#define LY_CTX_LEAFREF_EXTENDED 0x0200 /**< By default, path attribute of leafref accepts only path as defined in RFC 7950.
+                                        By using this option, the path attribute will also allow using XPath functions as deref() */
 
 /** @} contextoptions */
 
diff --git a/src/diff.c b/src/diff.c
index 51189d2..0625bc9 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -161,6 +161,127 @@
     return rc;
 }
 
+/**
+ * @brief Find metadata/an attribute of a node.
+ *
+ * @param[in] node Node to search.
+ * @param[in] name Metadata/attribute name.
+ * @param[out] meta Metadata found, NULL if not found.
+ * @param[out] attr Attribute found, NULL if not found.
+ */
+static void
+lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
+{
+    struct lyd_meta *m;
+    struct lyd_attr *a;
+
+    if (meta) {
+        *meta = NULL;
+    }
+    if (attr) {
+        *attr = NULL;
+    }
+
+    if (node->schema) {
+        assert(meta);
+
+        LY_LIST_FOR(node->meta, m) {
+            if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
+                *meta = m;
+                break;
+            }
+        }
+    } else {
+        assert(attr);
+
+        LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
+            /* name */
+            if (strcmp(a->name.name, name)) {
+                continue;
+            }
+
+            /* module */
+            switch (a->format) {
+            case LY_VALUE_JSON:
+                if (strcmp(a->name.module_name, "yang")) {
+                    continue;
+                }
+                break;
+            case LY_VALUE_XML:
+                if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
+                    continue;
+                }
+                break;
+            default:
+                LOGINT(LYD_CTX(node));
+                return;
+            }
+
+            *attr = a;
+            break;
+        }
+    }
+}
+
+/**
+ * @brief Learn operation of a diff node.
+ *
+ * @param[in] diff_node Diff node.
+ * @param[out] op Operation.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
+{
+    struct lyd_meta *meta = NULL;
+    struct lyd_attr *attr = NULL;
+    const struct lyd_node *diff_parent;
+    const char *str;
+    char *path;
+
+    for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
+        lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
+        if (!meta && !attr) {
+            continue;
+        }
+
+        str = meta ? lyd_get_meta_value(meta) : attr->value;
+        if ((str[0] == 'r') && (diff_parent != diff_node)) {
+            /* we do not care about this operation if it's in our parent */
+            continue;
+        }
+        *op = lyd_diff_str2op(str);
+        return LY_SUCCESS;
+    }
+
+    /* operation not found */
+    path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
+    LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
+    free(path);
+    return LY_EINT;
+}
+
+/**
+ * @brief Remove metadata/an attribute from a node.
+ *
+ * @param[in] node Node to update.
+ * @param[in] name Metadata/attribute name.
+ */
+static void
+lyd_diff_del_meta(struct lyd_node *node, const char *name)
+{
+    struct lyd_meta *meta;
+    struct lyd_attr *attr;
+
+    lyd_diff_find_meta(node, name, &meta, &attr);
+
+    if (meta) {
+        lyd_free_meta_single(meta);
+    } else if (attr) {
+        lyd_free_attr_single(LYD_CTX(node), attr);
+    }
+}
+
 LY_ERR
 lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value,
         const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position,
@@ -168,6 +289,9 @@
 {
     struct lyd_node *dup, *siblings, *match = NULL, *diff_parent = NULL, *elem;
     const struct lyd_node *parent = NULL;
+    enum lyd_diff_op cur_op;
+    struct lyd_meta *meta;
+    uint32_t diff_opts;
 
     assert(diff);
 
@@ -189,14 +313,16 @@
 
     /* find the first existing parent */
     siblings = *diff;
-    while (1) {
+    do {
         /* find next node parent */
         parent = node;
         while (parent->parent && (!diff_parent || (parent->parent->schema != diff_parent->schema))) {
             parent = lyd_parent(parent);
         }
-        if (parent == node) {
-            /* no more parents to find */
+
+        if (lysc_is_dup_inst_list(parent->schema)) {
+            /* assume it never exists, we are not able to distinguish whether it does or not */
+            match = NULL;
             break;
         }
 
@@ -210,41 +336,75 @@
 
         /* move down in the diff */
         siblings = lyd_child_no_keys(match);
-    }
+    } while (parent != node);
 
-    /* duplicate the subtree (and connect to the diff if possible) */
-    if (diff_parent) {
-        LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
-                LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
-    } else {
-        LY_CHECK_RET(lyd_dup_single(node, NULL,
-                LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
-    }
+    if (match && (parent == node)) {
+        /* special case when there is already an operation on our descendant */
+        assert(!lyd_diff_get_op(diff_parent, &cur_op) && (cur_op == LYD_DIFF_OP_NONE));
+        (void)cur_op;
 
-    /* find the first duplicated parent */
-    if (!diff_parent) {
-        diff_parent = lyd_parent(dup);
-        while (diff_parent && diff_parent->parent) {
-            diff_parent = lyd_parent(diff_parent);
+        /* move it to the end where it is expected (matters for user-ordered lists) */
+        if (lysc_is_userordered(diff_parent->schema)) {
+            for (elem = diff_parent; elem->next && (elem->next->schema == elem->schema); elem = elem->next) {}
+            if (elem != diff_parent) {
+                LY_CHECK_RET(lyd_insert_after(elem, diff_parent));
+            }
         }
-    } else {
-        diff_parent = dup;
-        while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
-            diff_parent = lyd_parent(diff_parent);
+
+        /* will be replaced by the new operation but keep the current op for descendants */
+        lyd_diff_del_meta(diff_parent, "operation");
+        LY_LIST_FOR(lyd_child_no_keys(diff_parent), elem) {
+            lyd_diff_find_meta(elem, "operation", &meta, NULL);
+            if (meta) {
+                /* explicit operation, fine */
+                continue;
+            }
+
+            /* set the none operation */
+            LY_CHECK_RET(lyd_new_meta(NULL, elem, NULL, "yang:operation", "none", 0, NULL));
         }
-    }
 
-    /* no parent existed, must be manually connected */
-    if (!diff_parent) {
-        /* there actually was no parent to duplicate */
-        lyd_insert_sibling(*diff, dup, diff);
-    } else if (!diff_parent->parent) {
-        lyd_insert_sibling(*diff, diff_parent, diff);
-    }
+        dup = diff_parent;
+    } else {
+        diff_opts = LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS;
+        if ((op != LYD_DIFF_OP_REPLACE) || !lysc_is_userordered(node->schema) || (node->schema->flags & LYS_CONFIG_R)) {
+            /* move applies only to the user-ordered list, no descendants */
+            diff_opts |= LYD_DUP_RECURSIVE;
+        }
 
-    /* add parent operation, if any */
-    if (diff_parent && (diff_parent != dup)) {
-        LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL));
+        /* duplicate the subtree (and connect to the diff if possible) */
+        if (diff_parent) {
+            LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
+                    diff_opts, &dup));
+        } else {
+            LY_CHECK_RET(lyd_dup_single(node, NULL, diff_opts, &dup));
+        }
+
+        /* find the first duplicated parent */
+        if (!diff_parent) {
+            diff_parent = lyd_parent(dup);
+            while (diff_parent && diff_parent->parent) {
+                diff_parent = lyd_parent(diff_parent);
+            }
+        } else {
+            diff_parent = dup;
+            while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
+                diff_parent = lyd_parent(diff_parent);
+            }
+        }
+
+        /* no parent existed, must be manually connected */
+        if (!diff_parent) {
+            /* there actually was no parent to duplicate */
+            lyd_insert_sibling(*diff, dup, diff);
+        } else if (!diff_parent->parent) {
+            lyd_insert_sibling(*diff, diff_parent, diff);
+        }
+
+        /* add parent operation, if any */
+        if (diff_parent && (diff_parent != dup)) {
+            LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL));
+        }
     }
 
     /* add subtree operation */
@@ -366,7 +526,7 @@
     LY_ERR rc = LY_SUCCESS;
     const struct lysc_node *schema;
     size_t buflen, bufused;
-    uint32_t first_pos, second_pos;
+    uint32_t first_pos, second_pos, comp_opts;
 
     assert(first || second);
 
@@ -402,7 +562,8 @@
     } else if (!first) {
         *op = LYD_DIFF_OP_CREATE;
     } else {
-        if (lyd_compare_single(second, userord_item->inst[second_pos], 0)) {
+        comp_opts = lysc_is_dup_inst_list(second->schema) ? LYD_COMPARE_FULL_RECURSION : 0;
+        if (lyd_compare_single(second, userord_item->inst[second_pos], comp_opts)) {
             /* in first, there is a different instance on the second position, we are going to move 'first' node */
             *op = LYD_DIFF_OP_REPLACE;
         } else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) {
@@ -928,98 +1089,6 @@
 }
 
 /**
- * @brief Find metadata/an attribute of a node.
- *
- * @param[in] node Node to search.
- * @param[in] name Metadata/attribute name.
- * @param[out] meta Metadata found, NULL if not found.
- * @param[out] attr Attribute found, NULL if not found.
- */
-static void
-lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
-{
-    struct lyd_meta *m;
-    struct lyd_attr *a;
-
-    *meta = NULL;
-    *attr = NULL;
-
-    if (node->schema) {
-        LY_LIST_FOR(node->meta, m) {
-            if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
-                *meta = m;
-                break;
-            }
-        }
-    } else {
-        LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
-            /* name */
-            if (strcmp(a->name.name, name)) {
-                continue;
-            }
-
-            /* module */
-            switch (a->format) {
-            case LY_VALUE_JSON:
-                if (strcmp(a->name.module_name, "yang")) {
-                    continue;
-                }
-                break;
-            case LY_VALUE_XML:
-                if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
-                    continue;
-                }
-                break;
-            default:
-                LOGINT(LYD_CTX(node));
-                return;
-            }
-
-            *attr = a;
-            break;
-        }
-    }
-}
-
-/**
- * @brief Learn operation of a diff node.
- *
- * @param[in] diff_node Diff node.
- * @param[out] op Operation.
- * @return LY_ERR value.
- */
-static LY_ERR
-lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
-{
-    struct lyd_meta *meta = NULL;
-    struct lyd_attr *attr = NULL;
-    const struct lyd_node *diff_parent;
-    const char *str;
-    char *path;
-
-    for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
-        lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
-        if (!meta && !attr) {
-            continue;
-        }
-
-        str = meta ? lyd_get_meta_value(meta) : attr->value;
-        if ((str[0] == 'r') && (diff_parent != diff_node)) {
-            /* we do not care about this operation if it's in our parent */
-            continue;
-        }
-        *op = lyd_diff_str2op(str);
-        return LY_SUCCESS;
-    }
-
-    /* operation not found */
-    path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
-    LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
-    free(path);
-    return LY_EINT;
-}
-
-/**
  * @brief Insert a diff node into a data tree.
  *
  * @param[in,out] first_node First sibling of the data tree.
@@ -1357,27 +1426,6 @@
 }
 
 /**
- * @brief Remove metadata/an attribute from a node.
- *
- * @param[in] node Node to update.
- * @param[in] name Metadata/attribute name.
- */
-static void
-lyd_diff_del_meta(struct lyd_node *node, const char *name)
-{
-    struct lyd_meta *meta;
-    struct lyd_attr *attr;
-
-    lyd_diff_find_meta(node, name, &meta, &attr);
-
-    if (meta) {
-        lyd_free_meta_single(meta);
-    } else if (attr) {
-        lyd_free_attr_single(LYD_CTX(node), attr);
-    }
-}
-
-/**
  * @brief Set a specific operation of a node. Delete the previous operation, if any.
  * Does not change the default flag.
  *
diff --git a/src/hash_table.c b/src/hash_table.c
index 0523d8e..246fb34 100644
--- a/src/hash_table.c
+++ b/src/hash_table.c
@@ -537,7 +537,7 @@
     int32_t i;
     ly_bool first_matched = 0;
     LY_ERR r, ret = LY_SUCCESS;
-    lyht_value_equal_cb old_val_equal;
+    lyht_value_equal_cb old_val_equal = NULL;
 
     LY_CHECK_ERR_RET(lyht_find_first(ht, hash, &rec), LOGARG(NULL, hash), LY_ENOTFOUND); /* hash not found */
 
diff --git a/src/hash_table.h b/src/hash_table.h
index c45a6da..13c6645 100644
--- a/src/hash_table.h
+++ b/src/hash_table.h
@@ -16,7 +16,6 @@
 #ifndef LY_HASH_TABLE_H_
 #define LY_HASH_TABLE_H_
 
-#include <pthread.h>
 #include <stddef.h>
 #include <stdint.h>
 
@@ -24,10 +23,15 @@
 extern "C" {
 #endif
 
-#include "compat.h"
 #include "log.h"
 
 /**
+ * @struct ly_ht
+ * @brief libyang hash table.
+ */
+struct ly_ht;
+
+/**
  * @brief Compute hash from (several) string(s).
  *
  * Usage:
diff --git a/src/log.c b/src/log.c
index 1a75dec..92f7c86 100644
--- a/src/log.c
+++ b/src/log.c
@@ -4,7 +4,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief Logger routines implementations
  *
- * Copyright (c) 2015 - 2022 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -48,12 +48,6 @@
 
 THREAD_LOCAL struct ly_log_location_s log_location = {0};
 
-LIBYANG_API_DEF const char *
-ly_last_errmsg(void)
-{
-    return last_msg;
-}
-
 LIBYANG_API_DEF LY_ERR
 ly_errcode(const struct ly_ctx *ctx)
 {
@@ -67,6 +61,47 @@
     return LY_SUCCESS;
 }
 
+LIBYANG_API_DEF const char *
+ly_strerrcode(LY_ERR err)
+{
+    /* ignore plugin flag */
+    err &= ~LY_EPLUGIN;
+
+    switch (err) {
+    case LY_SUCCESS:
+        return "Success";
+    case LY_EMEM:
+        return "Out of memory";
+    case LY_ESYS:
+        return "System call failed";
+    case LY_EINVAL:
+        return "Invalid value";
+    case LY_EEXIST:
+        return "Already exists";
+    case LY_ENOTFOUND:
+        return "Not found";
+    case LY_EINT:
+        return "Internal error";
+    case LY_EVALID:
+        return "Validation failed";
+    case LY_EDENIED:
+        return "Operation denied";
+    case LY_EINCOMPLETE:
+        return "Operation incomplete";
+    case LY_ERECOMPILE:
+        return "Recompilation required";
+    case LY_ENOT:
+        return "Negative result";
+    case LY_EOTHER:
+        return "Another failure reason";
+    case LY_EPLUGIN:
+        break;
+    }
+
+    /* unreachable */
+    return "Unknown";
+}
+
 LIBYANG_API_DEF LY_VECODE
 ly_vecode(const struct ly_ctx *ctx)
 {
@@ -81,6 +116,38 @@
 }
 
 LIBYANG_API_DEF const char *
+ly_strvecode(LY_VECODE vecode)
+{
+    switch (vecode) {
+    case LYVE_SUCCESS:
+        return "Success";
+    case LYVE_SYNTAX:
+        return "General syntax error";
+    case LYVE_SYNTAX_YANG:
+        return "YANG syntax error";
+    case LYVE_SYNTAX_YIN:
+        return "YIN syntax error";
+    case LYVE_REFERENCE:
+        return "Reference error";
+    case LYVE_XPATH:
+        return "XPath error";
+    case LYVE_SEMANTICS:
+        return "Semantic error";
+    case LYVE_SYNTAX_XML:
+        return "XML syntax error";
+    case LYVE_SYNTAX_JSON:
+        return "JSON syntax error";
+    case LYVE_DATA:
+        return "YANG data error";
+    case LYVE_OTHER:
+        return "Another error";
+    }
+
+    /* unreachable */
+    return "Unknown";
+}
+
+LIBYANG_API_DEF const char *
 ly_errmsg(const struct ly_ctx *ctx)
 {
     struct ly_err_item *i;
@@ -96,6 +163,12 @@
 }
 
 LIBYANG_API_DEF const char *
+ly_last_errmsg(void)
+{
+    return last_msg;
+}
+
+LIBYANG_API_DEF const char *
 ly_errpath(const struct ly_ctx *ctx)
 {
     struct ly_err_item *i;
@@ -634,6 +707,8 @@
     va_start(ap, format);
     log_vprintf(NULL, LY_LLDBG, 0, 0, NULL, NULL, dbg_format, ap);
     va_end(ap);
+
+    free(dbg_format);
 }
 
 #endif
diff --git a/src/log.h b/src/log.h
index 0d4a5d8..021ab89 100644
--- a/src/log.h
+++ b/src/log.h
@@ -170,7 +170,7 @@
  * @param[in] dbg_groups Bitfield of enabled debug message groups (see @ref dbggroup).
  * @return Previous options bitfield.
  */
-uint32_t ly_log_dbg_groups(uint32_t dbg_groups);
+LIBYANG_API_DECL uint32_t ly_log_dbg_groups(uint32_t dbg_groups);
 
 #endif
 
@@ -294,25 +294,31 @@
  * @brief Libyang full error structure.
  */
 struct ly_err_item {
-    LY_LOG_LEVEL level;
-    LY_ERR no;
-    LY_VECODE vecode;
-    char *msg;
-    char *path;
-    char *apptag;
-    struct ly_err_item *next;
-    struct ly_err_item *prev; /* first item's prev points to the last item */
+    LY_LOG_LEVEL level;         /**< error (message) log level */
+    LY_ERR no;                  /**< error code */
+    LY_VECODE vecode;           /**< validation error code, if any */
+    char *msg;                  /**< error message */
+    char *path;                 /**< error path that caused the error, if any */
+    char *apptag;               /**< error-app-tag, if any */
+    struct ly_err_item *next;   /**< next error item */
+    struct ly_err_item *prev;   /**< previous error item, points to the last item for the ifrst item */
 };
 
 /**
- * @brief Get the last (thread-specific) error message.
+ * @brief Get the last (thread, context-specific) error code.
  *
- * ::ly_errmsg() should be used instead of this function but this one is useful for getting
- * errors from functions that do not have any context accessible. Or as a simple unified logging API.
- *
- * @return Last generated error message.
+ * @param[in] ctx Relative context.
+ * @return LY_ERR value of the last error code.
  */
-LIBYANG_API_DECL const char *ly_last_errmsg(void);
+LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx);
+
+/**
+ * @brief Get human-readable error message for an error code.
+ *
+ * @param[in] err Error code.
+ * @return String error message.
+ */
+LIBYANG_API_DECL const char *ly_strerrcode(LY_ERR err);
 
 /**
  * @brief Get the last (thread, context-specific) validation error code.
@@ -325,12 +331,12 @@
 LIBYANG_API_DECL LY_VECODE ly_vecode(const struct ly_ctx *ctx);
 
 /**
- * @brief Get the last (thread, context-specific) error code.
+ * @brief Get human-readable error message for a validation error code.
  *
- * @param[in] ctx Relative context.
- * @return LY_ERR value of the last error code.
+ * @param[in] vecode Validation error code.
+ * @return String error message.
  */
-LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx);
+LIBYANG_API_DECL const char *ly_strvecode(LY_VECODE vecode);
 
 /**
  * @brief Get the last (thread, context-specific) error message. If the coresponding module defined
@@ -345,6 +351,16 @@
 LIBYANG_API_DECL const char *ly_errmsg(const struct ly_ctx *ctx);
 
 /**
+ * @brief Get the last (thread-specific) error message.
+ *
+ * ::ly_errmsg() should be used instead of this function but this one is useful for getting
+ * errors from functions that do not have any context accessible. Or as a simple unified logging API.
+ *
+ * @return Last generated error message.
+ */
+LIBYANG_API_DECL const char *ly_last_errmsg(void);
+
+/**
  * @brief Get the last (thread, context-specific) path of the element where was an error.
  *
  * The path always corresponds to the error message available via ::ly_errmsg(), so
diff --git a/src/parser_data.h b/src/parser_data.h
index 1bad5b2..537d6d4 100644
--- a/src/parser_data.h
+++ b/src/parser_data.h
@@ -312,7 +312,7 @@
 enum lyd_type {
     LYD_TYPE_DATA_YANG = 0,     /* generic YANG instance data */
     LYD_TYPE_RPC_YANG,          /* instance of a YANG RPC/action request with only "input" data children,
-                                   including all parents in case of an action */
+                                   including all parents and optional top-level "action" element in case of an action */
     LYD_TYPE_NOTIF_YANG,        /* instance of a YANG notification, including all parents in case of a nested one */
     LYD_TYPE_REPLY_YANG,        /* instance of a YANG RPC/action reply with only "output" data children,
                                    including all parents in case of an action */
diff --git a/src/parser_json.c b/src/parser_json.c
index 9cbd5d8..5143fae 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -338,6 +338,55 @@
 }
 
 /**
+ * @brief Get the hint for the data type parsers according to the current JSON parser context.
+ *
+ * @param[in] jsonctx JSON parser context. The context is supposed to be on a value.
+ * @param[in,out] status Pointer to the current context status,
+ * in some circumstances the function manipulates with the context so the status is updated.
+ * @param[out] type_hint_p Pointer to the variable to store the result.
+ * @return LY_SUCCESS in case of success.
+ * @return LY_EINVAL in case of invalid context status not referring to a value.
+ */
+static LY_ERR
+lydjson_value_type_hint(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p)
+{
+    *type_hint_p = 0;
+
+    if (*status_p == LYJSON_ARRAY) {
+        /* only [null] */
+        LY_CHECK_RET(lyjson_ctx_next(jsonctx, status_p));
+        if (*status_p != LYJSON_NULL) {
+            LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON,
+                    "Expected JSON name/value or special name/[null], but input data contains name/[%s].",
+                    lyjson_token2str(*status_p));
+            return LY_EINVAL;
+        }
+
+        LY_CHECK_RET(lyjson_ctx_next(jsonctx, NULL));
+        if (lyjson_ctx_status(jsonctx) != LYJSON_ARRAY_CLOSED) {
+            LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.",
+                    lyjson_token2str(*status_p));
+            return LY_EINVAL;
+        }
+
+        *type_hint_p = LYD_VALHINT_EMPTY;
+    } else if (*status_p == LYJSON_STRING) {
+        *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64;
+    } else if (*status_p == LYJSON_NUMBER) {
+        *type_hint_p = LYD_VALHINT_DECNUM;
+    } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) {
+        *type_hint_p = LYD_VALHINT_BOOLEAN;
+    } else if (*status_p == LYJSON_NULL) {
+        *type_hint_p = 0;
+    } else {
+        LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p));
+        return LY_EINVAL;
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
  * @brief Check that the input data are parseable as the @p list.
  *
  * Checks for all the list's keys. Function does not revert the context state.
@@ -354,7 +403,7 @@
     enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(jsonctx);
     struct ly_set key_set = {0};
     const struct lysc_node *snode;
-    uint32_t i;
+    uint32_t i, hints;
 
     assert(list && (list->nodetype == LYS_LIST));
 
@@ -402,7 +451,9 @@
                         goto cleanup;
                     }
 
-                    rc = lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL);
+                    rc = lydjson_value_type_hint(jsonctx, &status, &hints);
+                    LY_CHECK_GOTO(rc, cleanup);
+                    rc = ly_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL, hints);
                     LY_CHECK_GOTO(rc, cleanup);
 
                     /* key with a valid value, remove from the set */
@@ -430,55 +481,6 @@
 }
 
 /**
- * @brief Get the hint for the data type parsers according to the current JSON parser context.
- *
- * @param[in] lydctx JSON data parser context. The context is supposed to be on a value.
- * @param[in,out] status Pointer to the current context status,
- * in some circumstances the function manipulates with the context so the status is updated.
- * @param[out] type_hint_p Pointer to the variable to store the result.
- * @return LY_SUCCESS in case of success.
- * @return LY_EINVAL in case of invalid context status not referring to a value.
- */
-static LY_ERR
-lydjson_value_type_hint(struct lyd_json_ctx *lydctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p)
-{
-    *type_hint_p = 0;
-
-    if (*status_p == LYJSON_ARRAY) {
-        /* only [null] */
-        LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_p));
-        if (*status_p != LYJSON_NULL) {
-            LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON,
-                    "Expected JSON name/value or special name/[null], but input data contains name/[%s].",
-                    lyjson_token2str(*status_p));
-            return LY_EINVAL;
-        }
-
-        LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, NULL));
-        if (lyjson_ctx_status(lydctx->jsonctx) != LYJSON_ARRAY_CLOSED) {
-            LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.",
-                    lyjson_token2str(*status_p));
-            return LY_EINVAL;
-        }
-
-        *type_hint_p = LYD_VALHINT_EMPTY;
-    } else if (*status_p == LYJSON_STRING) {
-        *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64;
-    } else if (*status_p == LYJSON_NUMBER) {
-        *type_hint_p = LYD_VALHINT_DECNUM;
-    } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) {
-        *type_hint_p = LYD_VALHINT_BOOLEAN;
-    } else if (*status_p == LYJSON_NULL) {
-        *type_hint_p = 0;
-    } else {
-        LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p));
-        return LY_EINVAL;
-    }
-
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Check in advance if the input data are parsable according to the provided @p snode.
  *
  * Note that the checks are done only in case the LYD_PARSE_OPAQ is allowed. Otherwise the same checking
@@ -518,12 +520,10 @@
         case LYS_LEAFLIST:
         case LYS_LEAF:
             /* value may not be valid in which case we parse it as an opaque node */
-            ret = lydjson_value_type_hint(lydctx, &status, type_hint_p);
-            if (ret) {
+            if ((ret = lydjson_value_type_hint(jsonctx, &status, type_hint_p))) {
                 break;
             }
-
-            if (lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL)) {
+            if (ly_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL, *type_hint_p)) {
                 ret = LY_ENOT;
             }
             break;
@@ -536,7 +536,7 @@
             break;
         }
     } else if (snode->nodetype & LYD_NODE_TERM) {
-        ret = lydjson_value_type_hint(lydctx, &status, type_hint_p);
+        ret = lydjson_value_type_hint(jsonctx, &status, type_hint_p);
     }
 
     /* restore parser */
@@ -846,7 +846,7 @@
         LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup);
 
         /* get value hints */
-        LY_CHECK_GOTO(rc = lydjson_value_type_hint(lydctx, &status, &val_hints), cleanup);
+        LY_CHECK_GOTO(rc = lydjson_value_type_hint(lydctx->jsonctx, &status, &val_hints), cleanup);
 
         if (node->schema) {
             /* create metadata */
@@ -969,7 +969,7 @@
         dynamic = lydctx->jsonctx->dynamic;
         lydctx->jsonctx->dynamic = 0;
 
-        LY_CHECK_RET(lydjson_value_type_hint(lydctx, status_inner_p, &type_hint));
+        LY_CHECK_RET(lydjson_value_type_hint(lydctx->jsonctx, status_inner_p, &type_hint));
     }
 
     /* get the module name */
@@ -1236,14 +1236,17 @@
         enum LYJSON_PARSER_STATUS *status, struct lyd_node **node)
 {
     LY_ERR r, rc = LY_SUCCESS;
-    uint32_t prev_parse_opts, prev_int_opts;
+    uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts;
     struct ly_in in_start;
     char *val = NULL;
     const char *end;
-    struct lyd_node *tree = NULL;
+    struct lyd_node *child = NULL;
+    ly_bool log_node = 0;
 
     assert(snode->nodetype & LYD_NODE_ANY);
 
+    *node = NULL;
+
     /* status check according to allowed JSON types */
     if (snode->nodetype == LYS_ANYXML) {
         LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_ARRAY) && (*status != LYJSON_NUMBER) &&
@@ -1256,39 +1259,41 @@
     /* create any node */
     switch (*status) {
     case LYJSON_OBJECT:
+        /* create node */
+        r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+        assert(*node);
+        LOG_LOCSET(NULL, *node, NULL, NULL);
+        log_node = 1;
+
         /* parse any data tree with correct options, first backup the current options and then make the parser
          * process data as opaq nodes */
-        prev_parse_opts = lydctx->parse_opts;
         lydctx->parse_opts &= ~LYD_PARSE_STRICT;
         lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0);
-        prev_int_opts = lydctx->int_opts;
         lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS;
         lydctx->any_schema = snode;
 
         /* process the anydata content */
         do {
-            r = lydjson_subtree_r(lydctx, NULL, &tree, NULL);
+            r = lydjson_subtree_r(lydctx, NULL, &child, NULL);
             LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
 
             *status = lyjson_ctx_status(lydctx->jsonctx);
         } while (*status == LYJSON_OBJECT_NEXT);
 
-        /* restore parser options */
-        lydctx->parse_opts = prev_parse_opts;
-        lydctx->int_opts = prev_int_opts;
-        lydctx->any_schema = NULL;
-
         /* finish linking metadata */
-        r = lydjson_metadata_finish(lydctx, &tree);
+        r = lydjson_metadata_finish(lydctx, &child);
         LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
 
-        r = lyd_create_any(snode, tree, LYD_ANYDATA_DATATREE, 1, node);
-        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+        /* assign the data tree */
+        ((struct lyd_node_any *)*node)->value.tree = child;
+        child = NULL;
         break;
     case LYJSON_ARRAY:
         /* skip until the array end */
         in_start = *lydctx->jsonctx->in;
-        LY_CHECK_RET(lydjson_data_skip(lydctx->jsonctx));
+        LY_CHECK_GOTO(rc = lydjson_data_skip(lydctx->jsonctx), cleanup);
 
         /* return back by all the WS */
         end = lydctx->jsonctx->in->current;
@@ -1299,22 +1304,25 @@
         /* make a copy of the whole array and store it */
         if (asprintf(&val, "[%.*s", (int)(end - in_start.current), in_start.current) == -1) {
             LOGMEM(lydctx->jsonctx->ctx);
-            return LY_EMEM;
+            rc = LY_EMEM;
+            goto cleanup;
         }
         r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
-        LY_CHECK_ERR_GOTO(r, rc = r, val_err);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+        val = NULL;
         break;
     case LYJSON_STRING:
         /* string value */
         if (lydctx->jsonctx->dynamic) {
-            LY_CHECK_RET(lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node));
+            LY_CHECK_GOTO(rc = lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node), cleanup);
             lydctx->jsonctx->dynamic = 0;
         } else {
             val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len);
-            LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM);
+            LY_CHECK_ERR_GOTO(!val, LOGMEM(lydctx->jsonctx->ctx); rc = LY_EMEM, cleanup);
 
             r = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node);
-            LY_CHECK_ERR_GOTO(r, rc = r, val_err);
+            LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+            val = NULL;
         }
         break;
     case LYJSON_NUMBER:
@@ -1323,10 +1331,11 @@
         /* JSON value */
         assert(!lydctx->jsonctx->dynamic);
         val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len);
-        LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM);
+        LY_CHECK_ERR_GOTO(!val, LOGMEM(lydctx->jsonctx->ctx); rc = LY_EMEM, cleanup);
 
         r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node);
-        LY_CHECK_ERR_GOTO(r, rc = r, val_err);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+        val = NULL;
         break;
     case LYJSON_NULL:
         /* no value */
@@ -1334,14 +1343,20 @@
         LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         break;
     default:
-        LOGINT_RET(lydctx->jsonctx->ctx);
+        LOGINT(lydctx->jsonctx->ctx);
+        rc = LY_EINT;
+        goto cleanup;
     }
 
 cleanup:
-    return rc;
-
-val_err:
+    if (log_node) {
+        LOG_LOCBACK(0, 1, 0, 0);
+    }
+    lydctx->parse_opts = prev_parse_opts;
+    lydctx->int_opts = prev_int_opts;
+    lydctx->any_schema = NULL;
     free(val);
+    lyd_free_tree(child);
     return rc;
 }
 
@@ -1531,7 +1546,7 @@
     size_t name_len, prefix_len = 0;
     ly_bool is_meta = 0, parse_subtree;
     const struct lysc_node *snode = NULL;
-    struct lysc_ext_instance *ext;
+    struct lysc_ext_instance *ext = NULL;
     struct lyd_node *node = NULL, *attr_node = NULL;
     const struct ly_ctx *ctx = lydctx->jsonctx->ctx;
     char *value = NULL;
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
index 9ea6397..2e3d0c5 100644
--- a/src/parser_lyb.c
+++ b/src/parser_lyb.c
@@ -1168,7 +1168,7 @@
 
     /* create node */
     ret = lyd_create_opaq(ctx, name, strlen(name), prefix, ly_strlen(prefix), module_key, ly_strlen(module_key),
-            value, strlen(value), &dynamic, format, val_prefix_data, 0, &node);
+            value, strlen(value), &dynamic, format, val_prefix_data, LYD_HINT_DATA, &node);
     LY_CHECK_GOTO(ret, cleanup);
 
     /* process children */
diff --git a/src/parser_xml.c b/src/parser_xml.c
index 54e6c8b..1914e76 100644
--- a/src/parser_xml.c
+++ b/src/parser_xml.c
@@ -75,6 +75,8 @@
 
     *meta = NULL;
 
+    LOG_LOCSET(sparent, NULL, NULL, NULL);
+
     /* check for NETCONF filter unqualified attributes */
     if (!strcmp(sparent->module->name, "notifications")) {
         /* ancient module that does not even use the extension */
@@ -163,6 +165,7 @@
     }
 
 cleanup:
+    LOG_LOCBACK(1, 0, 0, 0);
     if (ret) {
         lyd_free_meta_siblings(*meta);
         *meta = NULL;
@@ -286,7 +289,7 @@
         assert(xmlctx->status == LYXML_ELEM_CONTENT);
         if (i < key_set.count) {
             /* validate the value */
-            r = lys_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns);
+            r = ly_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA);
             if (!r) {
                 /* key with a valid value, remove from the set */
                 ly_set_rm_index(&key_set, i, NULL);
@@ -392,7 +395,7 @@
 
     if ((*snode)->nodetype & LYD_NODE_TERM) {
         /* value may not be valid in which case we parse it as an opaque node */
-        if (lys_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns)) {
+        if (ly_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA)) {
             LOGVRB("Parsing opaque term node \"%s\" with invalid value \"%.*s\".", (*snode)->name, xmlctx->value_len,
                     xmlctx->value);
             *snode = NULL;
@@ -853,6 +856,7 @@
     uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts;
     struct lyd_node *child = NULL;
     char *val = NULL;
+    ly_bool log_node = 0;
 
     *node = NULL;
 
@@ -878,6 +882,14 @@
         LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
         val = NULL;
     } else {
+        /* create node */
+        r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node);
+        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+
+        assert(*node);
+        LOG_LOCSET(NULL, *node, NULL, NULL);
+        log_node = 1;
+
         /* parser next */
         r = lyxml_ctx_next(xmlctx);
         LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
@@ -893,17 +905,15 @@
             LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup);
         }
 
-        /* restore options */
-        lydctx->parse_opts = prev_parse_opts;
-        lydctx->int_opts = prev_int_opts;
-
-        /* create node */
-        r = lyd_create_any(snode, child, LYD_ANYDATA_DATATREE, 1, node);
-        LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
+        /* assign the data tree */
+        ((struct lyd_node_any *)*node)->value.tree = child;
         child = NULL;
     }
 
 cleanup:
+    if (log_node) {
+        LOG_LOCBACK(0, 1, 0, 0);
+    }
     lydctx->parse_opts = prev_parse_opts;
     lydctx->int_opts = prev_int_opts;
     free(val);
@@ -1087,7 +1097,11 @@
     const char *prefix;
     size_t prefix_len;
 
-    assert(xmlctx->status == LYXML_ELEMENT);
+    if (xmlctx->status != LYXML_ELEMENT) {
+        /* nothing to parse */
+        return LY_ENOT;
+    }
+
     if (ly_strncmp(name, xmlctx->name, xmlctx->name_len)) {
         /* not the expected element */
         return LY_ENOT;
@@ -1147,7 +1161,8 @@
 {
     LY_ERR r, rc = LY_SUCCESS;
     struct lyd_xml_ctx *lydctx;
-    ly_bool parsed_data_nodes = 0;
+    ly_bool parsed_data_nodes = 0, close_elem = 0;
+    struct lyd_node *act = NULL;
     enum LYXML_PARSER_STATUS status;
 
     assert(ctx && in && lydctx_p);
@@ -1167,6 +1182,14 @@
     /* find the operation node if it exists already */
     LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup);
 
+    if ((int_opts & LYD_INTOPT_RPC) && (int_opts & LYD_INTOPT_ACTION)) {
+        /* can be either, try to parse "action" */
+        if (!lydxml_envelope(lydctx->xmlctx, "action", "urn:ietf:params:xml:ns:yang:1", 0, &act)) {
+            close_elem = 1;
+            int_opts &= ~LYD_INTOPT_RPC;
+        }
+    }
+
     /* parse XML data */
     while (lydctx->xmlctx->status == LYXML_ELEMENT) {
         r = lydxml_subtree_r(lydctx, parent, first_p, parsed);
@@ -1179,6 +1202,19 @@
         }
     }
 
+    /* close an opened element */
+    if (close_elem) {
+        if (lydctx->xmlctx->status != LYXML_ELEM_CLOSE) {
+            assert(lydctx->xmlctx->status == LYXML_ELEMENT);
+            LOGVAL(lydctx->xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\".",
+                    (int)lydctx->xmlctx->name_len, lydctx->xmlctx->name);
+            rc = LY_EVALID;
+            goto cleanup;
+        }
+
+        LY_CHECK_GOTO(rc = lyxml_ctx_next(lydctx->xmlctx), cleanup);
+    }
+
     /* check final state */
     if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) {
         LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node.");
@@ -1211,6 +1247,7 @@
     assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count &&
             !lydctx->node_when.count));
 
+    lyd_free_tree(act);
     if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) {
         lyd_xml_ctx_free((struct lyd_ctx *)lydctx);
     } else {
@@ -1241,12 +1278,6 @@
 
     assert(envp && !*envp);
 
-    if (xmlctx->status != LYXML_ELEMENT) {
-        /* nothing to parse */
-        assert(xmlctx->status == LYXML_END);
-        goto cleanup;
-    }
-
     /* parse "rpc" */
     r = lydxml_envelope(xmlctx, "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp);
     LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
@@ -1295,12 +1326,6 @@
 
     assert(envp && !*envp);
 
-    if (xmlctx->status != LYXML_ELEMENT) {
-        /* nothing to parse */
-        assert(xmlctx->status == LYXML_END);
-        goto cleanup;
-    }
-
     /* parse "notification" */
     r = lydxml_envelope(xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0", 0, envp);
     LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
@@ -1716,12 +1741,6 @@
 
     assert(envp && !*envp);
 
-    if (xmlctx->status != LYXML_ELEMENT) {
-        /* nothing to parse */
-        assert(xmlctx->status == LYXML_END);
-        goto cleanup;
-    }
-
     /* parse "rpc-reply" */
     r = lydxml_envelope(xmlctx, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp);
     LY_CHECK_ERR_GOTO(r, rc = r, cleanup);
diff --git a/src/parser_yang.c b/src/parser_yang.c
index dd84480..b81cf60 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -804,7 +804,8 @@
             MOVE_INPUT(ctx, 1);
             goto extension;
         case '{':
-            /* allowed only for input and output statements which can be without arguments */
+        case ';':
+            /* allowed only for input and output statements which are without arguments */
             if ((*kw == LY_STMT_INPUT) || (*kw == LY_STMT_OUTPUT)) {
                 break;
             }
@@ -2860,11 +2861,6 @@
         YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, inout_p->exts, ret, cleanup);
     }
 
-    if (!inout_p->child) {
-        LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "data-def-stmt", lyplg_ext_stmt2str(inout_kw));
-        return LY_EVALID;
-    }
-
 cleanup:
     return ret;
 }
diff --git a/src/parser_yin.c b/src/parser_yin.c
index fa44968..088e79d 100644
--- a/src/parser_yin.c
+++ b/src/parser_yin.c
@@ -3718,7 +3718,6 @@
                 ret = LY_EINT;
             }
             LY_CHECK_GOTO(ret, cleanup);
-            subelem = NULL;
 
             LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup);
         }
diff --git a/src/path.c b/src/path.c
index cfa934f..d691696 100644
--- a/src/path.c
+++ b/src/path.c
@@ -264,6 +264,60 @@
     return LY_EVALID;
 }
 
+/**
+ * @brief Parse deref XPath function and perform all additional checks.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] ctx_node Optional context node, used for logging.
+ * @param[in] exp Parsed path.
+ * @param[in,out] tok_idx Index in @p exp, is adjusted.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_parse_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const struct lyxp_expr *exp,
+        uint32_t *tok_idx)
+{
+    size_t arg_len;
+    uint32_t begin_token, end_token;
+    struct lyxp_expr *arg_expr = NULL;
+
+    /* mandatory FunctionName */
+    LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_FUNCNAME), LY_EVALID);
+    if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "deref", 5)) {
+        LOGVAL(ctx, LYVE_XPATH, "Unexpected XPath function \"%.*s\" in path, expected \"deref(...)\"",
+                exp->tok_len[*tok_idx], exp->tok_pos[*tok_idx]);
+        return LY_EVALID;
+    }
+
+    /* mandatory '(' */
+    LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR1), LY_EVALID);
+    begin_token = *tok_idx;
+
+    /* count tokens till ')' */
+    while (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_PAR2) && *tok_idx < exp->used) {
+        /* emebedded functions are not allowed */
+        if (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_FUNCNAME)) {
+            LOGVAL(ctx, LYVE_XPATH, "Embedded function XPath function inside deref function within the path"
+                    "is not allowed");
+            return LY_EVALID;
+        }
+
+        (*tok_idx)++;
+    }
+
+    /* mandatory ')' */
+    LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR2), LY_EVALID);
+    end_token = *tok_idx - 1;
+
+    /* parse the path of deref argument */
+    arg_len = exp->tok_pos[end_token] - exp->tok_pos[begin_token];
+    LY_CHECK_RET(ly_path_parse(ctx, ctx_node, &exp->expr[exp->tok_pos[begin_token]], arg_len, 1,
+            LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &arg_expr), LY_EVALID);
+    lyxp_expr_free(ctx, arg_expr);
+
+    return LY_SUCCESS;
+}
+
 LY_ERR
 ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len,
         ly_bool lref, uint16_t begin, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr)
@@ -294,12 +348,23 @@
         if (lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_OPER_PATH)) {
             /* relative path check specific to leafref */
             if (lref) {
+                /* optional function 'deref..' */
+                if ((ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) &&
+                        !lyxp_check_token(NULL, exp, tok_idx, LYXP_TOKEN_FUNCNAME)) {
+                    LY_CHECK_ERR_GOTO(ly_path_parse_deref(ctx, ctx_node, exp, &tok_idx), ret = LY_EVALID, error);
+
+                    /* '/' */
+                    LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID,
+                            error);
+                }
+
                 /* mandatory '..' */
                 LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_DDOT), ret = LY_EVALID, error);
 
                 do {
                     /* '/' */
-                    LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error);
+                    LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID,
+                            error);
 
                     /* optional '..' */
                 } while (!lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_DDOT));
@@ -866,6 +931,187 @@
 }
 
 /**
+ * @brief Duplicate ly_path_predicate structure.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] pred The array of path predicates.
+ * @param[out] dup Duplicated predicates.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_dup_predicates(const struct ly_ctx *ctx, const struct ly_path_predicate *pred, struct ly_path_predicate **dup)
+{
+    LY_ARRAY_COUNT_TYPE u;
+
+    if (!pred) {
+        return LY_SUCCESS;
+    }
+
+    LY_ARRAY_CREATE_RET(ctx, *dup, LY_ARRAY_COUNT(pred), LY_EMEM);
+    LY_ARRAY_FOR(pred, u) {
+        LY_ARRAY_INCREMENT(*dup);
+        (*dup)[u].type = pred->type;
+
+        switch (pred->type) {
+        case LY_PATH_PREDTYPE_POSITION:
+            /* position-predicate */
+            (*dup)[u].position = pred->position;
+            break;
+        case LY_PATH_PREDTYPE_LIST:
+        case LY_PATH_PREDTYPE_LEAFLIST:
+            /* key-predicate or leaf-list-predicate */
+            (*dup)[u].key = pred->key;
+            pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].value);
+            LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount);
+            break;
+        case LY_PATH_PREDTYPE_LIST_VAR:
+            /* key-predicate with a variable */
+            (*dup)[u].key = pred->key;
+            (*dup)[u].variable = strdup(pred->variable);
+            break;
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Appends path elements from source to destination array
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] src The source path
+ * @param[in,out] dst The destination path
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_append(const struct ly_ctx *ctx, const struct ly_path *src, struct ly_path **dst)
+{
+    LY_ERR ret = LY_SUCCESS;
+    LY_ARRAY_COUNT_TYPE u;
+    struct ly_path *p;
+
+    if (!src) {
+        return LY_SUCCESS;
+    }
+
+    LY_ARRAY_CREATE_RET(ctx, *dst, LY_ARRAY_COUNT(src), LY_EMEM);
+    LY_ARRAY_FOR(src, u) {
+        LY_ARRAY_NEW_GOTO(ctx, *dst, p, ret, cleanup);
+        p->node = src[u].node;
+        p->ext = src[u].ext;
+        LY_CHECK_GOTO(ret = ly_path_dup_predicates(ctx, src[u].predicates, &p->predicates), cleanup);
+    }
+
+cleanup:
+    return ret;
+}
+
+/**
+ * @brief Compile deref XPath function into ly_path structure.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] ctx_node Optional context node, mandatory of @p lref.
+ * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find
+ * the top-level node inside the extension instance instead of a module. Note that this is the case not only if
+ * the @p ctx_node is NULL, but also if the relative path starting in @p ctx_node reaches the document root
+ * via double dots.
+ * @param[in] expr Parsed path.
+ * @param[in] oper Oper option (@ref path_oper_options).
+ * @param[in] target Target option (@ref path_target_options).
+ * @param[in] format Format of the path.
+ * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix).
+ * @param[in,out] tok_idx Index in @p exp, is adjusted.
+ * @param[out] path Compiled path.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node,
+        const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target,
+        LY_VALUE_FORMAT format, void *prefix_data, uint32_t *tok_idx, struct ly_path **path)
+{
+    LY_ERR ret = LY_SUCCESS;
+    struct lyxp_expr expr2;
+    struct ly_path *path2 = NULL;
+    const struct lysc_node *node2;
+    const struct lysc_node_leaf *deref_leaf_node;
+    const struct lysc_type_leafref *lref;
+    uint32_t begin_token;
+
+    *path = NULL;
+
+    /* properly parsed path must always starts with 'deref' and '(' */
+    assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_FUNCNAME));
+    assert(!strncmp(&expr->expr[expr->tok_pos[*tok_idx]], "deref", 5));
+    (*tok_idx)++;
+    assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR1));
+    (*tok_idx)++;
+    begin_token = *tok_idx;
+
+    /* emebedded functions were already identified count tokens till ')' */
+    while (lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2) && (*tok_idx < expr->used)) {
+        (*tok_idx)++;
+    }
+
+    /* properly parsed path must have ')' within the tokens */
+    assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2));
+
+    /* prepare expr representing just deref arg */
+    expr2.tokens = &expr->tokens[begin_token];
+    expr2.tok_pos = &expr->tok_pos[begin_token];
+    expr2.tok_len = &expr->tok_len[begin_token];
+    expr2.repeat = &expr->repeat[begin_token];
+    expr2.used = *tok_idx - begin_token;
+    expr2.size = expr->size - begin_token;
+    expr2.expr = expr->expr;
+
+    /* compile just deref arg, append it to the path and find dereferenced lref for next operations */
+    LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, ctx_node, top_ext, &expr2, oper, target, format, prefix_data,
+            &path2), cleanup);
+    node2 = path2[LY_ARRAY_COUNT(path2) - 1].node;
+    deref_leaf_node = (const struct lysc_node_leaf *)node2;
+    lref = (const struct lysc_type_leafref *)deref_leaf_node->type;
+    LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup);
+    ly_path_free(ctx, path2);
+    path2 = NULL;
+
+    /* compile dereferenced leafref expression and append it to the path */
+    LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, lref->path, oper, target, format, prefix_data,
+            &path2), cleanup);
+    node2 = path2[LY_ARRAY_COUNT(path2) - 1].node;
+    LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup);
+    ly_path_free(ctx, path2);
+    path2 = NULL;
+
+    /* properly parsed path must always continue with ')' and '/' */
+    assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2));
+    (*tok_idx)++;
+    assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH));
+    (*tok_idx)++;
+
+    /* prepare expr representing rest of the path after deref */
+    expr2.tokens = &expr->tokens[*tok_idx];
+    expr2.tok_pos = &expr->tok_pos[*tok_idx];
+    expr2.tok_len = &expr->tok_len[*tok_idx];
+    expr2.repeat = &expr->repeat[*tok_idx];
+    expr2.used = expr->used - *tok_idx;
+    expr2.size = expr->size - *tok_idx;
+    expr2.expr = expr->expr;
+
+    /* compile rest of the path and append it to the path */
+    LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, &expr2, oper, target, format, prefix_data, &path2),
+            cleanup);
+    LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup);
+
+cleanup:
+    ly_path_free(ctx, path2);
+    if (ret) {
+        ly_path_free(ctx, *path);
+        *path = NULL;
+    }
+    return ret;
+}
+
+/**
  * @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled.
  *
  * @param[in] ctx libyang context.
@@ -922,7 +1168,13 @@
         getnext_opts = 0;
     }
 
-    if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
+    if (lref && (ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) &&
+            (expr->tokens[tok_idx] == LYXP_TOKEN_FUNCNAME)) {
+        /* deref function */
+        ret = ly_path_compile_deref(ctx, ctx_node, top_ext, expr, oper, target, format, prefix_data, &tok_idx, path);
+        ctx_node = (*path)[LY_ARRAY_COUNT(*path) - 1].node;
+        goto cleanup;
+    } else if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
         /* absolute path */
         ctx_node = NULL;
 
@@ -1158,7 +1410,8 @@
 LY_ERR
 ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path **dup)
 {
-    LY_ARRAY_COUNT_TYPE u, v;
+    LY_ERR ret = LY_SUCCESS;
+    LY_ARRAY_COUNT_TYPE u;
 
     if (!path) {
         return LY_SUCCESS;
@@ -1168,34 +1421,8 @@
     LY_ARRAY_FOR(path, u) {
         LY_ARRAY_INCREMENT(*dup);
         (*dup)[u].node = path[u].node;
-        if (path[u].predicates) {
-            LY_ARRAY_CREATE_RET(ctx, (*dup)[u].predicates, LY_ARRAY_COUNT(path[u].predicates), LY_EMEM);
-            LY_ARRAY_FOR(path[u].predicates, v) {
-                struct ly_path_predicate *pred = &path[u].predicates[v];
-
-                LY_ARRAY_INCREMENT((*dup)[u].predicates);
-                (*dup)[u].predicates[v].type = pred->type;
-
-                switch (pred->type) {
-                case LY_PATH_PREDTYPE_POSITION:
-                    /* position-predicate */
-                    (*dup)[u].predicates[v].position = pred->position;
-                    break;
-                case LY_PATH_PREDTYPE_LIST:
-                case LY_PATH_PREDTYPE_LEAFLIST:
-                    /* key-predicate or leaf-list-predicate */
-                    (*dup)[u].predicates[v].key = pred->key;
-                    pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].predicates[v].value);
-                    LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount);
-                    break;
-                case LY_PATH_PREDTYPE_LIST_VAR:
-                    /* key-predicate with a variable */
-                    (*dup)[u].predicates[v].key = pred->key;
-                    (*dup)[u].predicates[v].variable = strdup(pred->variable);
-                    break;
-                }
-            }
-        }
+        (*dup)[u].ext = path[u].ext;
+        LY_CHECK_RET(ret = ly_path_dup_predicates(ctx, path[u].predicates, &(*dup)[u].predicates), ret);
     }
 
     return LY_SUCCESS;
diff --git a/src/plugins_exts/nacm.c b/src/plugins_exts/nacm.c
index 5ab8daa..df49721 100644
--- a/src/plugins_exts/nacm.c
+++ b/src/plugins_exts/nacm.c
@@ -101,7 +101,7 @@
 
     /* check for duplication */
     LY_ARRAY_FOR(parent->exts, u) {
-        if ((&parent->exts[u] != ext) && parent->exts[u].record && (parent->exts[u].record->plugin.id == ext->record->plugin.id)) {
+        if ((&parent->exts[u] != ext) && parent->exts[u].record && !strcmp(parent->exts[u].record->plugin.id, ext->record->plugin.id)) {
             /* duplication of a NACM extension on a single node
              * We check for all NACM plugins since we want to catch even the situation that there is default-deny-all
              * AND default-deny-write */
diff --git a/src/plugins_types.c b/src/plugins_types.c
index cb4b896..7f62be8 100644
--- a/src/plugins_types.c
+++ b/src/plugins_types.c
@@ -224,24 +224,52 @@
 }
 
 /**
- * @brief Simply return module local prefix. Also, store the module in a set.
+ * @brief Get prefix for XML print.
+ *
+ * @param[in] mod Module whose prefix to get.
+ * @param[in,out] prefix_data Set of used modules in the print. If @p mod is found in this set, no string (prefix) is
+ * returned.
+ * @return Prefix to print, may be NULL if the default namespace should be used.
  */
 static const char *
 ly_xml_get_prefix(const struct lys_module *mod, void *prefix_data)
 {
-    struct ly_set *ns_list = prefix_data;
+    struct ly_set *mods = prefix_data;
+    uint32_t i;
 
-    LY_CHECK_RET(ly_set_add(ns_list, (void *)mod, 0, NULL), NULL);
+    /* first is the local module */
+    assert(mods->count);
+    if (mods->objs[0] == mod) {
+        return NULL;
+    }
+
+    /* check for duplicates in the rest of the modules and add there */
+    for (i = 1; i < mods->count; ++i) {
+        if (mods->objs[i] == mod) {
+            break;
+        }
+    }
+    if (i == mods->count) {
+        LY_CHECK_RET(ly_set_add(mods, (void *)mod, 1, NULL), NULL);
+    }
+
+    /* return the prefix */
     return mod->prefix;
 }
 
 /**
- * @brief Simply return module name.
+ * @brief Get prefix for JSON print.
+ *
+ * @param[in] mod Module whose prefix to get.
+ * @param[in] prefix_data Current local module, may be NULL. If it matches @p mod, no string (preifx) is returned.
+ * @return Prefix (module name) to print, may be NULL if the default module should be used.
  */
 static const char *
-ly_json_get_prefix(const struct lys_module *mod, void *UNUSED(prefix_data))
+ly_json_get_prefix(const struct lys_module *mod, void *prefix_data)
 {
-    return mod->name;
+    const struct lys_module *local_mod = prefix_data;
+
+    return (local_mod == mod) ? NULL : mod->name;
 }
 
 const char *
diff --git a/src/plugins_types.h b/src/plugins_types.h
index 3ec1d3b..8490e3a 100644
--- a/src/plugins_types.h
+++ b/src/plugins_types.h
@@ -367,7 +367,7 @@
  * @param[in] format Format of the prefix (::lyplg_type_print_clb's format parameter).
  * @param[in] prefix_data Format-specific data (::lyplg_type_print_clb's prefix_data parameter).
  * @return Module prefix to print.
- * @return NULL on error.
+ * @return NULL on using the current module/namespace.
  */
 LIBYANG_API_DECL const char *lyplg_type_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data);
 
diff --git a/src/plugins_types/identityref.c b/src/plugins_types/identityref.c
index 8b7985d..e33b012 100644
--- a/src/plugins_types/identityref.c
+++ b/src/plugins_types/identityref.c
@@ -50,8 +50,16 @@
 identityref_ident2str(const struct lysc_ident *ident, LY_VALUE_FORMAT format, void *prefix_data, char **str, size_t *str_len)
 {
     int len;
+    const char *prefix;
 
-    len = asprintf(str, "%s:%s", lyplg_type_get_prefix(ident->module, format, prefix_data), ident->name);
+    /* get the prefix, may be NULL for no prefix and the default namespace */
+    prefix = lyplg_type_get_prefix(ident->module, format, prefix_data);
+
+    if (prefix) {
+        len = asprintf(str, "%s:%s", prefix, ident->name);
+    } else {
+        len = asprintf(str, "%s", ident->name);
+    }
     if (len == -1) {
         return LY_EMEM;
     }
@@ -269,8 +277,8 @@
             LY_CHECK_GOTO(ret, cleanup);
         }
     } else {
-        /* JSON format with prefix is the canonical one */
-        ret = identityref_ident2str(ident, LY_VALUE_JSON, NULL, &canon, NULL);
+        /* JSON format is the canonical one */
+        ret = identityref_ident2str(ident, LY_VALUE_JSON, ctx_node ? ctx_node->module : NULL, &canon, NULL);
         LY_CHECK_GOTO(ret, cleanup);
 
         ret = lydict_insert_zc(ctx, canon, &storage->_canonical);
diff --git a/src/plugins_types/instanceid.c b/src/plugins_types/instanceid.c
index 0eef9cc..53a1de1 100644
--- a/src/plugins_types/instanceid.c
+++ b/src/plugins_types/instanceid.c
@@ -50,12 +50,19 @@
     LY_ERR ret = LY_SUCCESS;
     LY_ARRAY_COUNT_TYPE u, v;
     char *result = NULL, quot;
-    const struct lys_module *mod = NULL;
+    const struct lys_module *mod = NULL, *local_mod = NULL;
+    struct ly_set *mods;
     ly_bool inherit_prefix = 0, d;
     const char *strval;
 
     switch (format) {
     case LY_VALUE_XML:
+        /* null the local module so that all the prefixes are printed */
+        mods = prefix_data;
+        local_mod = mods->objs[0];
+        mods->objs[0] = NULL;
+
+    /* fallthrough */
     case LY_VALUE_SCHEMA:
     case LY_VALUE_SCHEMA_RESOLVED:
         /* everything is prefixed */
@@ -136,6 +143,9 @@
     }
 
 cleanup:
+    if (local_mod) {
+        mods->objs[0] = (void *)local_mod;
+    }
     if (ret) {
         free(result);
     } else {
diff --git a/src/plugins_types/instanceid_keys.c b/src/plugins_types/instanceid_keys.c
index 163bf19..ab7751c 100644
--- a/src/plugins_types/instanceid_keys.c
+++ b/src/plugins_types/instanceid_keys.c
@@ -67,10 +67,18 @@
     void *mem;
     const char *cur_exp_ptr;
     ly_bool is_nt;
-    const struct lys_module *context_mod = NULL;
+    const struct lys_module *context_mod = NULL, *local_mod = NULL;
+    struct ly_set *mods;
 
     *str_value = NULL;
 
+    if (format == LY_VALUE_XML) {
+        /* null the local module so that all the prefixes are printed */
+        mods = prefix_data;
+        local_mod = mods->objs[0];
+        mods->objs[0] = NULL;
+    }
+
     while (cur_idx < val->keys->used) {
         cur_tok = val->keys->tokens[cur_idx];
         cur_exp_ptr = val->keys->expr + val->keys->tok_pos[cur_idx];
@@ -79,11 +87,15 @@
             /* tokens that may include prefixes, get them in the target format */
             is_nt = (cur_tok == LYXP_TOKEN_NAMETEST) ? 1 : 0;
             LY_CHECK_GOTO(ret = lyplg_type_xpath10_print_token(cur_exp_ptr, val->keys->tok_len[cur_idx], is_nt, &context_mod,
-                    val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), error);
+                    val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), cleanup);
 
             /* append the converted token */
             mem = realloc(*str_value, str_len + strlen(str_tok) + 1);
-            LY_CHECK_ERR_GOTO(!mem, free(str_tok), error_mem);
+            if (!mem) {
+                free(str_tok);
+                ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory.");
+                goto cleanup;
+            }
             *str_value = mem;
             str_len += sprintf(*str_value + str_len, "%s", str_tok);
             free(str_tok);
@@ -93,7 +105,10 @@
         } else {
             /* just copy the token */
             mem = realloc(*str_value, str_len + val->keys->tok_len[cur_idx] + 1);
-            LY_CHECK_GOTO(!mem, error_mem);
+            if (!mem) {
+                ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory.");
+                goto cleanup;
+            }
             *str_value = mem;
             str_len += sprintf(*str_value + str_len, "%.*s", (int)val->keys->tok_len[cur_idx], cur_exp_ptr);
 
@@ -102,13 +117,14 @@
         }
     }
 
-    return LY_SUCCESS;
-
-error_mem:
-    ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory.");
-
-error:
-    free(*str_value);
+cleanup:
+    if (local_mod) {
+        mods->objs[0] = (void *)local_mod;
+    }
+    if (ret) {
+        free(*str_value);
+        *str_value = NULL;
+    }
     return ret;
 }
 
diff --git a/src/plugins_types/node_instanceid.c b/src/plugins_types/node_instanceid.c
index 00cf207..7833263 100644
--- a/src/plugins_types/node_instanceid.c
+++ b/src/plugins_types/node_instanceid.c
@@ -50,7 +50,8 @@
     LY_ERR ret = LY_SUCCESS;
     LY_ARRAY_COUNT_TYPE u, v;
     char *result = NULL, quot;
-    const struct lys_module *mod = NULL;
+    const struct lys_module *mod = NULL, *local_mod = NULL;
+    struct ly_set *mods;
     ly_bool inherit_prefix = 0, d;
     const char *strval;
 
@@ -62,6 +63,12 @@
 
     switch (format) {
     case LY_VALUE_XML:
+        /* null the local module so that all the prefixes are printed */
+        mods = prefix_data;
+        local_mod = mods->objs[0];
+        mods->objs[0] = NULL;
+
+    /* fallthrough */
     case LY_VALUE_SCHEMA:
     case LY_VALUE_SCHEMA_RESOLVED:
         /* everything is prefixed */
@@ -148,6 +155,9 @@
     }
 
 cleanup:
+    if (local_mod) {
+        mods->objs[0] = (void *)local_mod;
+    }
     if (ret) {
         free(result);
     } else {
diff --git a/src/plugins_types/xpath1.0.c b/src/plugins_types/xpath1.0.c
index 6f05bd2..fdfc2a9 100644
--- a/src/plugins_types/xpath1.0.c
+++ b/src/plugins_types/xpath1.0.c
@@ -3,7 +3,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief ietf-yang-types xpath1.0 type plugin.
  *
- * Copyright (c) 2021 CESNET, z.s.p.o.
+ * Copyright (c) 2021 - 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -57,12 +57,9 @@
     while (!(ret = ly_value_prefix_next(str_begin, token + tok_len, &len, &is_prefix, &str_next)) && len) {
         if (!is_prefix) {
             if (!has_prefix && is_nametest && (get_format == LY_VALUE_XML) && *context_mod) {
-                /* prefix is always needed, get it in the target format */
+                /* get the prefix */
                 prefix = lyplg_type_get_prefix(*context_mod, get_format, get_prefix_data);
-                if (!prefix) {
-                    ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error.");
-                    goto cleanup;
-                }
+                assert(prefix);
 
                 /* append the nametest and prefix */
                 mem = realloc(str, str_len + strlen(prefix) + 1 + len + 1);
@@ -94,10 +91,7 @@
                 if (mod) {
                     /* get the prefix in the target format */
                     prefix = lyplg_type_get_prefix(mod, get_format, get_prefix_data);
-                    if (!prefix) {
-                        ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error.");
-                        goto cleanup;
-                    }
+                    assert(prefix);
                     pref_len = strlen(prefix);
                 } else {
                     /* invalid prefix, just copy it */
@@ -223,13 +217,25 @@
     LY_ERR ret = LY_SUCCESS;
     uint16_t expr_idx = 0;
     uint32_t str_len = 0;
+    const struct lys_module *local_mod = NULL;
+    struct ly_set *mods;
 
     *str_value = NULL;
     *err = NULL;
 
+    if (format == LY_VALUE_XML) {
+        /* null the local module so that all the prefixes are printed */
+        mods = prefix_data;
+        local_mod = mods->objs[0];
+        mods->objs[0] = NULL;
+    }
+
     /* recursively print the expression */
     ret = xpath10_print_subexpr_r(&expr_idx, 0, NULL, xp_val, format, prefix_data, str_value, &str_len, err);
 
+    if (local_mod) {
+        mods->objs[0] = (void *)local_mod;
+    }
     if (ret) {
         free(*str_value);
         *str_value = NULL;
diff --git a/src/printer_json.c b/src/printer_json.c
index a9dc2da..165dfab 100644
--- a/src/printer_json.c
+++ b/src/printer_json.c
@@ -14,6 +14,7 @@
  */
 
 #include <assert.h>
+#include <ctype.h>
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -219,31 +220,38 @@
 static LY_ERR
 json_print_string(struct ly_out *out, const char *text)
 {
-    uint64_t i, n;
+    uint64_t i;
 
     if (!text) {
         return LY_SUCCESS;
     }
 
     ly_write_(out, "\"", 1);
-    for (i = n = 0; text[i]; i++) {
-        const unsigned char ascii = text[i];
+    for (i = 0; text[i]; i++) {
+        const unsigned char byte = text[i];
 
-        if (ascii < 0x20) {
-            /* control character */
-            ly_print_(out, "\\u%.4X", ascii);
-        } else {
-            switch (ascii) {
-            case '"':
-                ly_print_(out, "\\\"");
-                break;
-            case '\\':
-                ly_print_(out, "\\\\");
-                break;
-            default:
+        switch (byte) {
+        case '"':
+            ly_print_(out, "\\\"");
+            break;
+        case '\\':
+            ly_print_(out, "\\\\");
+            break;
+        case '\r':
+            ly_print_(out, "\\r");
+            break;
+        case '\t':
+            ly_print_(out, "\\t");
+            break;
+        default:
+            if (iscntrl(byte)) {
+                /* control character */
+                ly_print_(out, "\\u%.4X", byte);
+            } else {
+                /* printable character (even non-ASCII UTF8) */
                 ly_write_(out, &text[i], 1);
-                n++;
             }
+            break;
         }
     }
     ly_write_(out, "\"", 1);
@@ -335,15 +343,19 @@
  * @param[in] pctx JSON printer context.
  * @param[in] ctx Context used to print the value.
  * @param[in] val Data value to be printed.
+ * @param[in] local_mod Module of the current node.
  * @return LY_ERR value.
  */
 static LY_ERR
-json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val)
+json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val,
+        const struct lys_module *local_mod)
 {
     ly_bool dynamic;
     LY_DATA_TYPE basetype;
-    const char *value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, NULL, &dynamic, NULL);
+    const char *value;
 
+    value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, (void *)local_mod, &dynamic, NULL);
+    LY_CHECK_RET(!value, LY_EINVAL);
     basetype = val->realtype->basetype;
 
 print_val:
@@ -406,7 +418,7 @@
     struct lyd_attr *attr;
 
     if (wdmod) {
-        ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name);
+        ly_print_(pctx->out, "%*s\"%s:default\":%strue", INDENT, wdmod->name, DO_FORMAT ? " " : "");
         LEVEL_PRINTED;
     }
 
@@ -443,14 +455,14 @@
     struct lyd_meta *meta;
 
     if (wdmod) {
-        ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name);
+        ly_print_(pctx->out, "%*s\"%s:default\":%strue", INDENT, wdmod->name, DO_FORMAT ? " " : "");
         LEVEL_PRINTED;
     }
 
     for (meta = node->meta; meta; meta = meta->next) {
         PRINT_COMMA;
         ly_print_(pctx->out, "%*s\"%s:%s\":%s", INDENT, meta->annotation->module->name, meta->name, DO_FORMAT ? " " : "");
-        LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value));
+        LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value, NULL));
         LEVEL_PRINTED;
     }
 
@@ -518,7 +530,7 @@
 json_print_leaf(struct jsonpr_ctx *pctx, const struct lyd_node *node)
 {
     LY_CHECK_RET(json_print_member(pctx, node, 0));
-    LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value));
+    LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module));
     LEVEL_PRINTED;
 
     /* print attributes as sibling */
@@ -783,7 +795,7 @@
     } else {
         assert(node->schema->nodetype == LYS_LEAFLIST);
 
-        LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value));
+        LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module));
 
         if (!pctx->print_sibling_metadata) {
             if ((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) {
@@ -900,7 +912,7 @@
             ly_print_(pctx->out, "%s", node->value);
         } else {
             /* string or a large number */
-            ly_print_(pctx->out, "\"%s\"", node->value);
+            json_print_string(pctx->out, node->value);
         }
         LEVEL_PRINTED;
 
diff --git a/src/printer_tree.c b/src/printer_tree.c
index d18485a..6aa2814 100644
--- a/src/printer_tree.c
+++ b/src/printer_tree.c
@@ -3426,7 +3426,7 @@
         /* current node is top-node */
         switch (tc->section) {
         case TRD_SECT_MODULE:
-            tc->cn = tc->cmod->data;
+            tc->cn = tc->cn->module->compiled->data;
             break;
         case TRD_SECT_RPCS:
             tc->cn = (const struct lysc_node *)tc->cmod->rpcs;
@@ -3486,7 +3486,8 @@
     }
 
     if (node->name.module_prefix) {
-        len += strlen(node->name.module_prefix);
+        /* prefix_name and ':' */
+        len += strlen(node->name.module_prefix) + 1;
     }
     if (node->name.str) {
         len += strlen(node->name.str);
@@ -3753,7 +3754,10 @@
     /* print node */
     ly_print_(pc->out, "\n");
     print_node = pc->fp.read.node(TRP_EMPTY_PARENT_CACHE, tc);
+    /* siblings do not print, so the node is always considered the last */
+    print_node.last_one = 1;
     max_gap_before_type = trb_max_gap_to_type(TRP_EMPTY_PARENT_CACHE, pc, tc);
+    tc->cn = node;
     trb_print_entire_node(&print_node, max_gap_before_type, wr, pc, tc);
 }
 
@@ -3856,25 +3860,33 @@
  * @param[in] compiled if @p ext is lysc structure.
  * @param[in] ext current processed extension.
  * @param[out] plug_ctx is plugin context which will be initialized.
+ * @param[out] ignore plugin callback is NULL.
  * @return LY_ERR value.
  */
 static LY_ERR
-tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx)
+tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx, ly_bool *ignore)
 {
     struct lysc_ext_instance *ext_comp;
     struct lysp_ext_instance *ext_pars;
+    const struct lyplg_ext *plugin;
     const char *flags = NULL, *add_opts = NULL;
 
     if (compiled) {
         ext_comp = ext;
-        if (ext_comp->def->plugin->printer_ctree) {
-            return ext_comp->def->plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts);
+        plugin = ext_comp->def->plugin;
+        if (!plugin->printer_ctree) {
+            *ignore = 1;
+            return LY_SUCCESS;
         }
+        return plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts);
     } else {
         ext_pars = ext;
-        if (ext_pars->record->plugin.printer_ptree) {
-            return ext_pars->record->plugin.printer_ptree(ext, plug_ctx, &flags, &add_opts);
+        plugin = &ext_pars->record->plugin;
+        if (!plugin->printer_ptree) {
+            *ignore = 1;
+            return LY_SUCCESS;
         }
+        return plugin->printer_ptree(ext, plug_ctx, &flags, &add_opts);
     }
 
     return LY_SUCCESS;
@@ -3992,7 +4004,7 @@
     LY_ARRAY_COUNT_TYPE i;
     uint64_t last_instance = UINT64_MAX;
     void *ext;
-    ly_bool child_exists;
+    ly_bool child_exists, ignore = 0;
     uint32_t max, max_gap_before_type = 0;
 
     ca = tro_parent_cache_for_child(ca, tc);
@@ -4010,8 +4022,12 @@
     while ((ext = trb_ext_iter(tc, &i))) {
         struct lyspr_tree_ctx plug_ctx = {0};
 
-        rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx);
+        rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore);
         LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end);
+        if (ignore) {
+            ignore = 0;
+            continue;
+        }
         trb_ext_try_unified_indent(&plug_ctx, ca, &max_gap_before_type, pc, tc);
         if (plug_ctx.schemas) {
             last_instance = i;
@@ -4030,8 +4046,12 @@
     while ((ext = trb_ext_iter(tc, &i))) {
         struct lyspr_tree_ctx plug_ctx = {0};
 
-        rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx);
+        rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore);
         LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end);
+        if (ignore) {
+            ignore = 0;
+            continue;
+        }
         if (!child_exists && (last_instance == i)) {
             trb_ext_print_schemas(&plug_ctx, 1, max_gap_before_type, wr, ca, pc, tc);
         } else {
@@ -4497,6 +4517,7 @@
     struct trt_printer_ctx pc_dupl;
     struct trt_tree_ctx tc_dupl;
     struct trt_node node;
+    ly_bool ignore = 0;
     uint32_t max_gap_before_type;
     void *ext;
 
@@ -4510,9 +4531,10 @@
     while ((ext = trb_mod_ext_iter(tc, &i))) {
         struct lyspr_tree_ctx plug_ctx = {0};
 
-        rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx);
+        rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore);
         LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end);
-        if (!plug_ctx.schemas) {
+        if (!plug_ctx.schemas || ignore) {
+            ignore = 0;
             continue;
         }
 
diff --git a/src/printer_xml.c b/src/printer_xml.c
index a7f4c73..47b0acc 100644
--- a/src/printer_xml.c
+++ b/src/printer_xml.c
@@ -178,6 +178,8 @@
     struct ly_set ns_list = {0};
     LY_ARRAY_COUNT_TYPE u;
     ly_bool dynamic, filter_attrs = 0;
+    const char *value;
+    uint32_t i;
 
     /* with-defaults */
     if (node->schema->nodetype & LYD_NODE_TERM) {
@@ -205,11 +207,14 @@
     }
 
     for (meta = node->meta; meta; meta = meta->next) {
-        const char *value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list,
-                &dynamic, NULL);
+        /* store the module of the default namespace, NULL because there is none */
+        ly_set_add(&ns_list, NULL, 0, NULL);
+
+        /* print the value */
+        value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);
 
         /* print namespaces connected with the value's prefixes */
-        for (uint32_t i = 0; i < ns_list.count; ++i) {
+        for (i = 1; i < ns_list.count; ++i) {
             mod = ns_list.objs[i];
             xml_print_ns(pctx, mod->ns, mod->prefix, 1);
         }
@@ -314,22 +319,32 @@
 static LY_ERR
 xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node)
 {
+    LY_ERR rc = LY_SUCCESS;
     struct ly_set ns_list = {0};
-    ly_bool dynamic;
-    const char *value;
+    ly_bool dynamic = 0;
+    const char *value = NULL;
+    const struct lys_module *mod;
+    uint32_t i;
 
-    xml_print_node_open(pctx, &node->node);
+    /* store the module of the default namespace */
+    if ((rc = ly_set_add(&ns_list, node->schema->module, 0, NULL))) {
+        LOGMEM(pctx->ctx);
+        goto cleanup;
+    }
+
+    /* print the value */
     value = ((struct lysc_node_leaf *)node->schema)->type->plugin->print(LYD_CTX(node), &node->value, LY_VALUE_XML,
             &ns_list, &dynamic, NULL);
-    LY_CHECK_RET(!value, LY_EINVAL);
+    LY_CHECK_ERR_GOTO(!value, rc = LY_EINVAL, cleanup);
+
+    /* print node opening */
+    xml_print_node_open(pctx, &node->node);
 
     /* print namespaces connected with the values's prefixes */
-    for (uint32_t u = 0; u < ns_list.count; ++u) {
-        const struct lys_module *mod = (const struct lys_module *)ns_list.objs[u];
-
+    for (i = 1; i < ns_list.count; ++i) {
+        mod = ns_list.objs[i];
         ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns);
     }
-    ly_set_erase(&ns_list, NULL);
 
     if (!value[0]) {
         ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
@@ -338,11 +353,13 @@
         lyxml_dump_text(pctx->out, value, 0);
         ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
     }
+
+cleanup:
+    ly_set_erase(&ns_list, NULL);
     if (dynamic) {
         free((void *)value);
     }
-
-    return LY_SUCCESS;
+    return rc;
 }
 
 /**
diff --git a/src/schema_compile.c b/src/schema_compile.c
index 001a3af..9d77a1f 100644
--- a/src/schema_compile.c
+++ b/src/schema_compile.c
@@ -437,6 +437,10 @@
 
     assert(implement || mod_p);
 
+    if (mod_p) {
+        *mod_p = NULL;
+    }
+
     for (i = 0; i < expr->used; ++i) {
         if ((expr->tokens[i] != LYXP_TOKEN_NAMETEST) && (expr->tokens[i] != LYXP_TOKEN_LITERAL)) {
             /* token cannot have a prefix */
@@ -476,38 +480,6 @@
 }
 
 /**
- * @brief Check and optionally implement modules referenced by a when expression.
- *
- * @param[in] ctx Compile context.
- * @param[in] when When to check.
- * @param[in,out] unres Global unres structure.
- * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for these whens to evaluate.
- * @return LY_ENOT if full check of this when should be skipped.
- * @return LY_ERR value on error.
- */
-static LY_ERR
-lys_compile_unres_when_implement(struct lysc_ctx *ctx, const struct lysc_when *when, struct lys_glob_unres *unres)
-{
-    LY_ERR rc = LY_SUCCESS;
-    const struct lys_module *mod = NULL;
-
-    /* check whether all the referenced modules are implemented */
-    rc = lys_compile_expr_implement(ctx->ctx, when->cond, LY_VALUE_SCHEMA_RESOLVED, when->prefixes,
-            ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod);
-    if (rc) {
-        goto cleanup;
-    } else if (mod) {
-        LOGWRN(ctx->ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
-                when->cond->expr, mod->name);
-        rc = LY_ENOT;
-        goto cleanup;
-    }
-
-cleanup:
-    return rc;
-}
-
-/**
  * @brief Check when for cyclic dependencies.
  *
  * @param[in] set Set with all the referenced nodes.
@@ -519,7 +491,7 @@
 {
     struct lyxp_set tmp_set;
     struct lyxp_set_scnode *xp_scnode;
-    uint32_t i, j;
+    uint32_t i, j, idx;
     LY_ARRAY_COUNT_TYPE u;
     LY_ERR ret = LY_SUCCESS;
 
@@ -565,36 +537,46 @@
                 }
 
                 for (j = 0; j < tmp_set.used; ++j) {
-                    /* skip roots'n'stuff */
-                    if (tmp_set.val.scnodes[j].type == LYXP_NODE_ELEM) {
-                        /* try to find this node in our set */
-                        uint32_t idx;
-
-                        if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) &&
-                                (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) {
-                            LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".",
-                                    tmp_set.val.scnodes[j].scnode->name);
-                            ret = LY_EVALID;
-                            LOG_LOCBACK(1, 0, 0, 0);
-                            goto cleanup;
-                        }
-
-                        /* needs to be checked, if in both sets, will be ignored */
-                        tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
-                    } else {
-                        /* no when, nothing to check */
+                    if (tmp_set.val.scnodes[j].type != LYXP_NODE_ELEM) {
+                        /* skip roots'n'stuff, no when, nothing to check */
                         tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_NODE;
+                        continue;
+                    }
+
+                    /* try to find this node in our set */
+                    if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) &&
+                            (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) {
+                        LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".",
+                                tmp_set.val.scnodes[j].scnode->name);
+                        ret = LY_EVALID;
+                        LOG_LOCBACK(1, 0, 0, 0);
+                        goto cleanup;
+                    }
+
+                    /* needs to be checked, if in both sets, will be ignored */
+                    tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
+                }
+
+                if (when->context != node) {
+                    /* node actually depends on this "when", not the context node */
+                    assert(tmp_set.val.scnodes[0].scnode == when->context);
+                    if (tmp_set.val.scnodes[0].in_ctx == LYXP_SET_SCNODE_START_USED) {
+                        /* replace the non-traversed context node with the dependent node */
+                        tmp_set.val.scnodes[0].scnode = (struct lysc_node *)node;
+                    } else {
+                        /* context node was traversed, so just add the dependent node */
+                        ret = lyxp_set_scnode_insert_node(&tmp_set, node, LYXP_SET_SCNODE_START_USED, LYXP_AXIS_CHILD, NULL);
+                        LY_CHECK_ERR_GOTO(ret, LOG_LOCBACK(1, 0, 0, 0), cleanup);
                     }
                 }
 
                 /* merge this set into the global when set */
                 lyxp_set_scnode_merge(set, &tmp_set);
             }
+            LOG_LOCBACK(1, 0, 0, 0);
 
             /* check when of non-data parents as well */
             node = node->parent;
-
-            LOG_LOCBACK(1, 0, 0, 0);
         } while (node && (node->nodetype & (LYS_CASE | LYS_CHOICE)));
 
         /* this node when was checked (xp_scnode could have been reallocd) */
@@ -640,6 +622,7 @@
 {
     struct lyxp_set tmp_set = {0};
     uint32_t i, opts;
+    struct lysc_node *schema;
     LY_ERR ret = LY_SUCCESS;
 
     opts = LYXP_SCNODE_SCHEMA | ((node->flags & LYS_IS_OUTPUT) ? LYXP_SCNODE_OUTPUT : 0);
@@ -655,28 +638,45 @@
     ctx->path[0] = '\0';
     lysc_path(node, LYSC_PATH_LOG, ctx->path, LYSC_CTX_BUFSIZE);
     for (i = 0; i < tmp_set.used; ++i) {
-        /* skip roots'n'stuff */
-        if ((tmp_set.val.scnodes[i].type == LYXP_NODE_ELEM) &&
-                (tmp_set.val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START_USED)) {
-            struct lysc_node *schema = tmp_set.val.scnodes[i].scnode;
+        if (tmp_set.val.scnodes[i].type != LYXP_NODE_ELEM) {
+            /* skip roots'n'stuff */
+            continue;
+        } else if (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START_USED) {
+            /* context node not actually traversed */
+            continue;
+        }
 
-            /* XPath expression cannot reference "lower" status than the node that has the definition */
-            if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module,
-                    schema->name)) {
-                LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr,
-                        (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name);
-            }
+        schema = tmp_set.val.scnodes[i].scnode;
 
-            /* check dummy node children/value accessing */
-            if (lysc_data_parent(schema) == node) {
-                LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children.");
-                ret = LY_EVALID;
-                goto cleanup;
-            } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) {
-                LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value.");
-                ret = LY_EVALID;
-                goto cleanup;
-            }
+        /* XPath expression cannot reference "lower" status than the node that has the definition */
+        if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module,
+                schema->name)) {
+            LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr,
+                    (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name);
+        }
+
+        /* check dummy node children/value accessing */
+        if (lysc_data_parent(schema) == node) {
+            LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children.");
+            ret = LY_EVALID;
+            goto cleanup;
+        } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) {
+            LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value.");
+            ret = LY_EVALID;
+            goto cleanup;
+        }
+    }
+
+    if (when->context != node) {
+        /* node actually depends on this "when", not the context node */
+        assert(tmp_set.val.scnodes[0].scnode == when->context);
+        if (tmp_set.val.scnodes[0].in_ctx == LYXP_SET_SCNODE_START_USED) {
+            /* replace the non-traversed context node with the dependent node */
+            tmp_set.val.scnodes[0].scnode = (struct lysc_node *)node;
+        } else {
+            /* context node was traversed, so just add the dependent node */
+            ret = lyxp_set_scnode_insert_node(&tmp_set, node, LYXP_SET_SCNODE_START_USED, LYXP_AXIS_CHILD, NULL);
+            LY_CHECK_GOTO(ret, cleanup);
         }
     }
 
@@ -695,20 +695,16 @@
  * @param[in] ctx Compile context.
  * @param[in] node Node to check.
  * @param[in] local_mods Sized array of local modules for musts of @p node at the same index.
- * @param[in,out] unres Global unres structure.
- * @return LY_ERECOMPILE
- * @return LY_ERR value
+ * @return LY_ERR value.
  */
 static LY_ERR
-lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods,
-        struct lys_glob_unres *unres)
+lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods)
 {
     struct lyxp_set tmp_set;
     uint32_t i, opts;
     LY_ARRAY_COUNT_TYPE u;
-    struct lysc_must *musts = NULL;
+    struct lysc_must *musts;
     LY_ERR ret = LY_SUCCESS;
-    const struct lys_module *mod;
     uint16_t flg;
 
     LOG_LOCSET(node, NULL, NULL, NULL);
@@ -718,18 +714,6 @@
 
     musts = lysc_node_musts(node);
     LY_ARRAY_FOR(musts, u) {
-        /* first check whether all the referenced modules are implemented */
-        mod = NULL;
-        ret = lys_compile_expr_implement(ctx->ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes,
-                ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod);
-        if (ret) {
-            goto cleanup;
-        } else if (mod) {
-            LOGWRN(ctx->ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
-                    musts[u].cond->expr, mod->name);
-            continue;
-        }
-
         /* check "must" */
         ret = lyxp_atomize(ctx->ctx, musts[u].cond, node->module, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, node,
                 node, &tmp_set, opts);
@@ -853,13 +837,11 @@
  * @param[in] node Context node for the leafref.
  * @param[in] lref Leafref to check/resolve.
  * @param[in] local_mod Local module for the leafref type.
- * @param[in,out] unres Global unres structure.
- * @return LY_ERECOMPILE if context recompilation is needed,
  * @return LY_ERR value.
  */
 static LY_ERR
 lys_compile_unres_leafref(struct lysc_ctx *ctx, const struct lysc_node *node, struct lysc_type_leafref *lref,
-        const struct lysp_module *local_mod, struct lys_glob_unres *unres)
+        const struct lysp_module *local_mod)
 {
     const struct lysc_node *target = NULL;
     struct ly_path *p;
@@ -873,9 +855,6 @@
         return LY_SUCCESS;
     }
 
-    /* first implement all the modules in the path */
-    LY_CHECK_RET(lys_compile_expr_implement(ctx->ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL));
-
     /* try to find the target, current module is that of the context node (RFC 7950 6.4.1 second bullet) */
     LY_CHECK_RET(ly_path_compile_leafref(ctx->ctx, node, ctx->ext, lref->path,
             (node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY,
@@ -945,6 +924,7 @@
  * @param[in] dflt_pmod Parsed module of the @p dflt to resolve possible prefixes.
  * @param[in,out] storage Storage for the compiled default value.
  * @param[in,out] unres Global unres structure for newly implemented modules.
+ * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for the value to be checked.
  * @return LY_ERR value.
  */
 static LY_ERR
@@ -1125,6 +1105,108 @@
 }
 
 /**
+ * @brief Implement all referenced modules by leafrefs, when and must conditions.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] unres Global unres structure with the sets to resolve.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERECOMPILE if the whole dep set needs to be recompiled with the new implemented modules.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+lys_compile_unres_depset_implement(struct ly_ctx *ctx, struct lys_glob_unres *unres)
+{
+    struct lys_depset_unres *ds_unres = &unres->ds_unres;
+    struct lysc_type_leafref *lref;
+    const struct lys_module *mod;
+    LY_ARRAY_COUNT_TYPE u;
+    struct lysc_unres_leafref *l;
+    struct lysc_unres_when *w;
+    struct lysc_unres_must *m;
+    struct lysc_must *musts;
+    ly_bool not_implemented;
+    uint32_t di = 0, li = 0, wi = 0, mi = 0;
+
+implement_all:
+    /* disabled leafrefs - even those because we need to check their target exists */
+    while (di < ds_unres->disabled_leafrefs.count) {
+        l = ds_unres->disabled_leafrefs.objs[di];
+
+        u = 0;
+        while ((lref = lys_type_leafref_next(l->node, &u))) {
+            LY_CHECK_RET(lys_compile_expr_implement(ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL));
+        }
+
+        ++di;
+    }
+
+    /* leafrefs */
+    while (li < ds_unres->leafrefs.count) {
+        l = ds_unres->leafrefs.objs[li];
+
+        u = 0;
+        while ((lref = lys_type_leafref_next(l->node, &u))) {
+            LY_CHECK_RET(lys_compile_expr_implement(ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL));
+        }
+
+        ++li;
+    }
+
+    /* when conditions */
+    while (wi < ds_unres->whens.count) {
+        w = ds_unres->whens.objs[wi];
+
+        LY_CHECK_RET(lys_compile_expr_implement(ctx, w->when->cond, LY_VALUE_SCHEMA_RESOLVED, w->when->prefixes,
+                ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod));
+        if (mod) {
+            LOGWRN(ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
+                    w->when->cond->expr, mod->name);
+
+            /* remove from the set to skip the check */
+            ly_set_rm_index(&ds_unres->whens, wi, free);
+            continue;
+        }
+
+        ++wi;
+    }
+
+    /* must conditions */
+    while (mi < ds_unres->musts.count) {
+        m = ds_unres->musts.objs[mi];
+
+        not_implemented = 0;
+        musts = lysc_node_musts(m->node);
+        LY_ARRAY_FOR(musts, u) {
+            LY_CHECK_RET(lys_compile_expr_implement(ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes,
+                    ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod));
+            if (mod) {
+                LOGWRN(ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.",
+                        musts[u].cond->expr, mod->name);
+
+                /* need to implement modules from all the expressions */
+                not_implemented = 1;
+            }
+        }
+
+        if (not_implemented) {
+            /* remove from the set to skip the check */
+            lysc_unres_must_free(m);
+            ly_set_rm_index(&ds_unres->musts, mi, NULL);
+            continue;
+        }
+
+        ++mi;
+    }
+
+    if ((di < ds_unres->disabled_leafrefs.count) || (li < ds_unres->leafrefs.count) || (wi < ds_unres->whens.count)) {
+        /* new items in the sets */
+        goto implement_all;
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
  * @brief Finish dependency set compilation by resolving all the unres sets.
  *
  * @param[in] ctx libyang context.
@@ -1136,7 +1218,7 @@
 static LY_ERR
 lys_compile_unres_depset(struct ly_ctx *ctx, struct lys_glob_unres *unres)
 {
-    LY_ERR ret = LY_SUCCESS, r;
+    LY_ERR ret = LY_SUCCESS;
     struct lysc_node *node;
     struct lysc_type *typeiter;
     struct lysc_type_leafref *lref;
@@ -1151,7 +1233,12 @@
     uint32_t i, processed_leafrefs = 0;
 
 resolve_all:
-    /* check disabled leafrefs first */
+    /* implement all referenced modules to get final ds_unres set */
+    if ((ret = lys_compile_unres_depset_implement(ctx, unres))) {
+        goto cleanup;
+    }
+
+    /* check disabled leafrefs */
     while (ds_unres->disabled_leafrefs.count) {
         /* remember index, it can change before we get to free this item */
         i = ds_unres->disabled_leafrefs.count - 1;
@@ -1161,7 +1248,7 @@
         LOG_LOCSET(l->node, NULL, NULL, NULL);
         v = 0;
         while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) {
-            ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres);
+            ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod);
         }
         LOG_LOCBACK(1, 0, 0, 0);
         LY_CHECK_GOTO(ret, cleanup);
@@ -1180,7 +1267,7 @@
         LOG_LOCSET(l->node, NULL, NULL, NULL);
         v = 0;
         while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) {
-            ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres);
+            ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod);
         }
         LOG_LOCBACK(1, 0, 0, 0);
         LY_CHECK_GOTO(ret, cleanup);
@@ -1204,29 +1291,7 @@
         processed_leafrefs++;
     }
 
-    /* check when, first implement all the referenced modules (for the cyclic check in the next loop to work) */
-    i = 0;
-    while (i < ds_unres->whens.count) {
-        w = ds_unres->whens.objs[i];
-        LYSC_CTX_INIT_PMOD(cctx, w->node->module->parsed, NULL);
-
-        LOG_LOCSET(w->node, NULL, NULL, NULL);
-        r = lys_compile_unres_when_implement(&cctx, w->when, unres);
-        LOG_LOCBACK(w->node ? 1 : 0, 0, 0, 0);
-
-        if (r == LY_ENOT) {
-            /* skip full when check, remove from the set */
-            free(w);
-            ly_set_rm_index(&ds_unres->whens, i, NULL);
-            continue;
-        } else if (r) {
-            /* error */
-            ret = r;
-            goto cleanup;
-        }
-
-        ++i;
-    }
+    /* check when, the referenced modules must be implemented now */
     while (ds_unres->whens.count) {
         i = ds_unres->whens.count - 1;
         w = ds_unres->whens.objs[i];
@@ -1248,7 +1313,7 @@
         LYSC_CTX_INIT_PMOD(cctx, m->node->module->parsed, m->ext);
 
         LOG_LOCSET(m->node, NULL, NULL, NULL);
-        ret = lys_compile_unres_must(&cctx, m->node, m->local_mods, unres);
+        ret = lys_compile_unres_must(&cctx, m->node, m->local_mods);
         LOG_LOCBACK(1, 0, 0, 0);
         LY_CHECK_GOTO(ret, cleanup);
 
@@ -1289,7 +1354,7 @@
         ly_set_rm_index(&ds_unres->dflts, i, NULL);
     }
 
-    /* some unres items may have been added */
+    /* some unres items may have been added by the default values */
     if ((processed_leafrefs != ds_unres->leafrefs.count) || ds_unres->disabled_leafrefs.count ||
             ds_unres->whens.count || ds_unres->musts.count || ds_unres->dflts.count) {
         goto resolve_all;
diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c
index 8c5a9e7..2723716 100644
--- a/src/schema_compile_node.c
+++ b/src/schema_compile_node.c
@@ -3417,7 +3417,7 @@
             /* YANG 1.0 denies key to be of empty type */
             if (key->type->basetype == LY_TYPE_EMPTY) {
                 LOGVAL(ctx->ctx, LYVE_SEMANTICS,
-                        "List's key cannot be of \"empty\" type until it is in YANG 1.1 module.");
+                        "List key of the \"empty\" type is allowed only in YANG 1.1 modules.");
                 return LY_EVALID;
             }
         } else {
diff --git a/src/tree_data.c b/src/tree_data.c
index d38acf2..c7fb06e 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -99,7 +99,7 @@
 lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, struct lyd_node **first_p,
         struct ly_in *in, LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op)
 {
-    LY_ERR r, rc = LY_SUCCESS;
+    LY_ERR r = LY_SUCCESS, rc = LY_SUCCESS;
     struct lyd_ctx *lydctx = NULL;
     struct ly_set parsed = {0};
     struct lyd_node *first;
@@ -1372,28 +1372,30 @@
             iter1 = lyd_child(node1);
             iter2 = lyd_child(node2);
 
-            if (!(node1->schema->flags & LYS_KEYLESS) && !(options & LYD_COMPARE_FULL_RECURSION)) {
-                /* lists with keys, their equivalence is based on their keys */
-                for (const struct lysc_node *key = lysc_node_child(node1->schema);
-                        key && (key->flags & LYS_KEY);
-                        key = key->next) {
-                    if (!iter1 || !iter2) {
-                        return (iter1 == iter2) ? LY_SUCCESS : LY_ENOT;
-                    }
-                    r = lyd_compare_single_schema(iter1, iter2, options, 1);
-                    LY_CHECK_RET(r);
-                    r = lyd_compare_single_data(iter1, iter2, options);
-                    LY_CHECK_RET(r);
-
-                    iter1 = iter1->next;
-                    iter2 = iter2->next;
-                }
-
-                return LY_SUCCESS;
-            } else {
-                /* lists without keys, their equivalence is based on equivalence of all the children (both direct and indirect) */
+            if (options & LYD_COMPARE_FULL_RECURSION) {
                 return lyd_compare_siblings_(iter1, iter2, options, 1);
+            } else if (node1->schema->flags & LYS_KEYLESS) {
+                /* always equal */
+                return LY_SUCCESS;
             }
+
+            /* lists with keys, their equivalence is based on their keys */
+            for (const struct lysc_node *key = lysc_node_child(node1->schema);
+                    key && (key->flags & LYS_KEY);
+                    key = key->next) {
+                if (!iter1 || !iter2) {
+                    return (iter1 == iter2) ? LY_SUCCESS : LY_ENOT;
+                }
+                r = lyd_compare_single_schema(iter1, iter2, options, 1);
+                LY_CHECK_RET(r);
+                r = lyd_compare_single_data(iter1, iter2, options);
+                LY_CHECK_RET(r);
+
+                iter1 = iter1->next;
+                iter2 = iter2->next;
+            }
+
+            return LY_SUCCESS;
         case LYS_ANYXML:
         case LYS_ANYDATA:
             any1 = (struct lyd_node_any *)node1;
@@ -1834,10 +1836,11 @@
  * @return LY_ERR value.
  */
 static LY_ERR
-lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, const struct lyd_node_inner *parent,
-        uint32_t options, struct lyd_node **dup_parent, struct lyd_node_inner **local_parent)
+lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent,
+        uint32_t options, struct lyd_node **dup_parent, struct lyd_node **local_parent)
 {
-    const struct lyd_node_inner *orig_parent, *iter;
+    const struct lyd_node *orig_parent;
+    struct lyd_node *iter;
     ly_bool repeat = 1, ext_parent = 0;
 
     *dup_parent = NULL;
@@ -1846,7 +1849,7 @@
     if (node->flags & LYD_EXT) {
         ext_parent = 1;
     }
-    for (orig_parent = node->parent; repeat && orig_parent; orig_parent = orig_parent->parent) {
+    for (orig_parent = lyd_parent(node); repeat && orig_parent; orig_parent = lyd_parent(orig_parent)) {
         if (ext_parent) {
             /* use the standard context */
             trg_ctx = LYD_CTX(orig_parent);
@@ -1857,32 +1860,23 @@
             repeat = 0;
         } else if (parent && (LYD_CTX(parent) != LYD_CTX(orig_parent)) &&
                 lyd_compare_schema_equal(parent->schema, orig_parent->schema) &&
-                lyd_compare_schema_parents_equal(&parent->node, &orig_parent->node)) {
+                lyd_compare_schema_parents_equal(parent, orig_parent)) {
             iter = parent;
             repeat = 0;
         } else {
             iter = NULL;
-            LY_CHECK_RET(lyd_dup_r((struct lyd_node *)orig_parent, trg_ctx, NULL, 0, (struct lyd_node **)&iter, options,
-                    (struct lyd_node **)&iter));
+            LY_CHECK_RET(lyd_dup_r(orig_parent, trg_ctx, NULL, 0, &iter, options, &iter));
         }
+
         if (!*local_parent) {
-            *local_parent = (struct lyd_node_inner *)iter;
+            /* update local parent (created parent) */
+            *local_parent = iter;
         }
-        if (iter->child) {
-            /* 1) list - add after keys
-             * 2) provided parent with some children */
-            iter->child->prev->next = *dup_parent;
-            if (*dup_parent) {
-                (*dup_parent)->prev = iter->child->prev;
-                iter->child->prev = *dup_parent;
-            }
-        } else {
-            ((struct lyd_node_inner *)iter)->child = *dup_parent;
-        }
+
         if (*dup_parent) {
-            (*dup_parent)->parent = (struct lyd_node_inner *)iter;
+            lyd_insert_node(iter, NULL, *dup_parent, 0);
         }
-        *dup_parent = (struct lyd_node *)iter;
+        *dup_parent = iter;
         if (orig_parent->flags & LYD_EXT) {
             ext_parent = 1;
         }
@@ -1890,8 +1884,8 @@
 
     if (repeat && parent) {
         /* given parent and created parents chain actually do not interconnect */
-        LOGERR(trg_ctx, LY_EINVAL,
-                "Invalid argument parent (%s()) - does not interconnect with the created node's parents chain.", __func__);
+        LOGERR(trg_ctx, LY_EINVAL, "None of the duplicated node \"%s\" schema parents match the provided parent \"%s\".",
+                LYD_NAME(node), LYD_NAME(parent));
         return LY_EINVAL;
     }
 
@@ -1899,14 +1893,14 @@
 }
 
 static LY_ERR
-lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node_inner *parent, uint32_t options,
+lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent, uint32_t options,
         ly_bool nosiblings, struct lyd_node **dup)
 {
     LY_ERR rc;
     const struct lyd_node *orig;          /* original node to be duplicated */
     struct lyd_node *first = NULL;        /* the first duplicated node, this is returned */
     struct lyd_node *top = NULL;          /* the most higher created node */
-    struct lyd_node_inner *local_parent = NULL; /* the direct parent node for the duplicated node(s) */
+    struct lyd_node *local_parent = NULL; /* the direct parent node for the duplicated node(s) */
 
     assert(node && trg_ctx);
 
@@ -1921,7 +1915,7 @@
         if (lysc_is_key(orig->schema)) {
             if (local_parent) {
                 /* the key must already exist in the parent */
-                rc = lyd_find_sibling_schema(local_parent->child, orig->schema, first ? NULL : &first);
+                rc = lyd_find_sibling_schema(lyd_child(local_parent), orig->schema, first ? NULL : &first);
                 LY_CHECK_ERR_GOTO(rc, LOGINT(trg_ctx), error);
             } else {
                 assert(!(options & LYD_DUP_WITH_PARENTS));
@@ -1931,8 +1925,7 @@
             }
         } else {
             /* if there is no local parent, it will be inserted into first */
-            rc = lyd_dup_r(orig, trg_ctx, local_parent ? &local_parent->node : NULL, 0, &first, options,
-                    first ? NULL : &first);
+            rc = lyd_dup_r(orig, trg_ctx, local_parent, 0, &first, options, first ? NULL : &first);
             LY_CHECK_GOTO(rc, error);
         }
         if (nosiblings) {
@@ -1989,7 +1982,7 @@
     LY_CHECK_ARG_RET(NULL, node, LY_EINVAL);
     LY_CHECK_RET(lyd_dup_ctx_check(node, parent));
 
-    return lyd_dup(node, LYD_CTX(node), parent, options, 1, dup);
+    return lyd_dup(node, LYD_CTX(node), (struct lyd_node *)parent, options, 1, dup);
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -1998,7 +1991,7 @@
 {
     LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL);
 
-    return lyd_dup(node, trg_ctx, parent, options, 1, dup);
+    return lyd_dup(node, trg_ctx, (struct lyd_node *)parent, options, 1, dup);
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -2007,7 +2000,7 @@
     LY_CHECK_ARG_RET(NULL, node, LY_EINVAL);
     LY_CHECK_RET(lyd_dup_ctx_check(node, parent));
 
-    return lyd_dup(node, LYD_CTX(node), parent, options, 0, dup);
+    return lyd_dup(node, LYD_CTX(node), (struct lyd_node *)parent, options, 0, dup);
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -2016,7 +2009,7 @@
 {
     LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL);
 
-    return lyd_dup(node, trg_ctx, parent, options, 0, dup);
+    return lyd_dup(node, trg_ctx, (struct lyd_node *)parent, options, 0, dup);
 }
 
 LIBYANG_API_DEF LY_ERR
@@ -2485,7 +2478,8 @@
 
             /* parent */
             parent = (depth > 1) ? dnodes->dnodes[depth - 2] : NULL;
-            assert(!parent || !iter->schema || !parent->schema || (lysc_data_parent(iter->schema) == parent->schema) ||
+            assert(!parent || !iter->schema || !parent->schema || (parent->schema->nodetype & LYD_NODE_ANY) ||
+                    (lysc_data_parent(iter->schema) == parent->schema) ||
                     (!lysc_data_parent(iter->schema) && (LYD_CTX(iter) != LYD_CTX(parent))));
 
             /* get module to print, if any */
@@ -2619,14 +2613,14 @@
     siblings = lyd_first_sibling(siblings);
 
     parent = siblings->parent;
-    if (parent && parent->schema && parent->children_ht) {
+    if (target->schema && parent && parent->schema && parent->children_ht) {
         assert(target->hash);
 
         if (lysc_is_dup_inst_list(target->schema)) {
             /* we must search the instances from beginning to find the first matching one */
             found = 0;
             LYD_LIST_FOR_INST(siblings, target->schema, iter) {
-                if (!lyd_compare_single(target, iter, 0)) {
+                if (!lyd_compare_single(target, iter, LYD_COMPARE_FULL_RECURSION)) {
                     found = 1;
                     break;
                 }
@@ -2646,10 +2640,16 @@
             }
         }
     } else {
-        /* no children hash table */
+        /* no children hash table or cannot be used */
         for ( ; siblings; siblings = siblings->next) {
-            if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) {
-                break;
+            if (lysc_is_dup_inst_list(target->schema)) {
+                if (!lyd_compare_single(siblings, target, LYD_COMPARE_FULL_RECURSION)) {
+                    break;
+                }
+            } else {
+                if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) {
+                    break;
+                }
             }
         }
     }
@@ -2736,6 +2736,7 @@
 {
     struct lyd_node **match_p, *first, *iter;
     struct lyd_node_inner *parent;
+    uint32_t comp_opts;
 
     LY_CHECK_ARG_RET(NULL, target, set, LY_EINVAL);
     LY_CHECK_CTX_EQUAL_RET(siblings ? LYD_CTX(siblings) : NULL, LYD_CTX(target), LY_EINVAL);
@@ -2747,6 +2748,9 @@
         return LY_ENOTFOUND;
     }
 
+    /* set options */
+    comp_opts = LYD_COMPARE_OPAQ | (lysc_is_dup_inst_list(target->schema) ? LYD_COMPARE_FULL_RECURSION : 0);
+
     /* get first sibling */
     siblings = lyd_first_sibling(siblings);
 
@@ -2771,7 +2775,7 @@
             }
             while (iter) {
                 /* add all found nodes into the set */
-                if ((iter != first) && !lyd_compare_single(iter, target, 0) && ly_set_add(*set, iter, 1, NULL)) {
+                if ((iter != first) && !lyd_compare_single(iter, target, comp_opts) && ly_set_add(*set, iter, 1, NULL)) {
                     goto error;
                 }
 
@@ -2786,7 +2790,7 @@
     } else {
         /* no children hash table */
         LY_LIST_FOR(siblings, siblings) {
-            if (!lyd_compare_single(target, siblings, LYD_COMPARE_OPAQ)) {
+            if (!lyd_compare_single(target, siblings, comp_opts)) {
                 ly_set_add(*set, (void *)siblings, 1, NULL);
             }
         }
diff --git a/src/tree_data.h b/src/tree_data.h
index dddeb6c..2c3066d 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -2144,7 +2144,7 @@
  * @param[in] first First data tree.
  * @param[in] second Second data tree.
  * @param[in] options Bitmask of options flags, see @ref diffoptions.
- * @param[out] diff Generated diff.
+ * @param[out] diff Generated diff, NULL if there are no differences.
  * @return LY_SUCCESS on success,
  * @return LY_ERR on error.
  */
@@ -2159,7 +2159,7 @@
  * @param[in] first First data tree.
  * @param[in] second Second data tree.
  * @param[in] options Bitmask of options flags, see @ref diffoptions.
- * @param[out] diff Generated diff.
+ * @param[out] diff Generated diff, NULL if there are no differences.
  * @return LY_SUCCESS on success,
  * @return LY_ERR on error.
  */
diff --git a/src/tree_data_common.c b/src/tree_data_common.c
index 69c3851..4602e98 100644
--- a/src/tree_data_common.c
+++ b/src/tree_data_common.c
@@ -499,8 +499,8 @@
 }
 
 LY_ERR
-lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
-        LY_VALUE_FORMAT format, void *prefix_data)
+ly_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
+        LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints)
 {
     LY_ERR rc = LY_SUCCESS;
     struct ly_err_item *err = NULL;
@@ -515,8 +515,8 @@
     }
 
     type = ((struct lysc_node_leaf *)node)->type;
-    rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data,
-            LYD_HINT_SCHEMA, node, &storage, NULL, &err);
+    rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data, hints, node,
+            &storage, NULL, &err);
     if (rc == LY_EINCOMPLETE) {
         /* actually success since we do not provide the context tree and call validation with
          * LY_TYPE_OPTS_INCOMPLETE_DATA */
@@ -748,8 +748,8 @@
     assert(!node->schema);
 
     /* get all keys into a set */
-    while ((key = lys_getnext(key, snode, NULL, 0)) && (snode->flags & LYS_KEY)) {
-        LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)snode, 1, NULL), cleanup);
+    while ((key = lys_getnext(key, snode, NULL, 0)) && (key->flags & LYS_KEY)) {
+        LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)key, 1, NULL), cleanup);
     }
 
     LY_LIST_FOR(lyd_child(node), child) {
@@ -778,8 +778,8 @@
         ly_set_rm_index(&key_set, i, NULL);
 
         /* check value */
-        ret = lys_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format,
-                opaq_k->val_prefix_data);
+        ret = ly_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format,
+                opaq_k->val_prefix_data, opaq_k->hints);
         LY_CHECK_GOTO(ret, cleanup);
     }
 
@@ -889,7 +889,7 @@
 
     if (snode->nodetype & LYD_NODE_TERM) {
         /* leaf / leaf-list */
-        rc = lys_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data);
+        rc = ly_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data, opaq->hints);
         LY_CHECK_GOTO(rc, cleanup);
     } else if (snode->nodetype == LYS_LIST) {
         /* list */
diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h
index e87723b..c7449ce 100644
--- a/src/tree_data_internal.h
+++ b/src/tree_data_internal.h
@@ -506,8 +506,7 @@
         const struct lyd_node *ctx_node, const struct lyd_node *tree);
 
 /**
- * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value coming
- * from a schema.
+ * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value.
  *
  * This function check just the type's restriction, if you want to check also the data tree context (e.g. in case of
  * require-instance restriction), use ::lyd_value_validate().
@@ -518,11 +517,12 @@
  * @param[in] value_len Length of the given @p value (mandatory).
  * @param[in] format Value prefix format.
  * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix).
+ * @param[in] hints Value encoding hints.
  * @return LY_SUCCESS on success
  * @return LY_ERR value if an error occurred.
  */
-LY_ERR lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
-        LY_VALUE_FORMAT format, void *prefix_data);
+LY_ERR ly_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
+        LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints);
 
 /**
  * @defgroup datahash Data nodes hash manipulation
diff --git a/src/tree_data_new.c b/src/tree_data_new.c
index 2f92c7e..090e7ac 100644
--- a/src/tree_data_new.c
+++ b/src/tree_data_new.c
@@ -1527,7 +1527,7 @@
     }
 
     /* parse path */
-    LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL,
+    LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST,
             LY_PATH_PRED_SIMPLE, &exp), cleanup);
 
     /* compile path */
@@ -1903,9 +1903,7 @@
     }
 
     LYD_TREE_DFS_BEGIN(tree, node) {
-        /* skip added default nodes */
-        if (((node->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) &&
-                (node->schema->nodetype & LYD_NODE_INNER)) {
+        if (node->schema->nodetype & LYD_NODE_INNER) {
             LY_CHECK_GOTO(ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &node_when, NULL,
                     NULL, implicit_options, diff), cleanup);
         }
@@ -1990,16 +1988,12 @@
 
     /* process nested nodes */
     LY_LIST_FOR(*tree, root) {
-        /* skip added default nodes */
-        if ((root->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) {
-            LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup);
+        LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup);
 
-            if (d) {
-                /* merge into one diff */
-                lyd_insert_sibling(*diff, d, diff);
-
-                d = NULL;
-            }
+        if (d) {
+            /* merge into one diff */
+            lyd_insert_sibling(*diff, d, diff);
+            d = NULL;
         }
     }
 
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 487a473..7a119b9 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -442,7 +442,7 @@
         struct ly_set **set)
 {
     LY_ERR ret = LY_SUCCESS;
-    struct lyxp_set xp_set;
+    struct lyxp_set xp_set = {0};
     struct lyxp_expr *exp = NULL;
     uint32_t i;
 
@@ -455,7 +455,9 @@
         ctx = ctx_node->module->ctx;
     }
 
-    memset(&xp_set, 0, sizeof xp_set);
+    /* allocate return set */
+    ret = ly_set_new(set);
+    LY_CHECK_GOTO(ret, cleanup);
 
     /* compile expression */
     ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp);
@@ -465,10 +467,6 @@
     ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options);
     LY_CHECK_GOTO(ret, cleanup);
 
-    /* allocate return set */
-    ret = ly_set_new(set);
-    LY_CHECK_GOTO(ret, cleanup);
-
     /* transform into ly_set */
     (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
     LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup);
@@ -501,15 +499,15 @@
         options = LYXP_SCNODE;
     }
 
+    /* allocate return set */
+    ret = ly_set_new(set);
+    LY_CHECK_GOTO(ret, cleanup);
+
     /* atomize expression */
     ret = lyxp_atomize(cur_mod->ctx, expr, cur_mod, LY_VALUE_SCHEMA_RESOLVED, (void *)prefixes, ctx_node, ctx_node,
             &xp_set, options);
     LY_CHECK_GOTO(ret, cleanup);
 
-    /* allocate return set */
-    ret = ly_set_new(set);
-    LY_CHECK_GOTO(ret, cleanup);
-
     /* transform into ly_set */
     (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
     LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(cur_mod->ctx); ret = LY_EMEM, cleanup);
@@ -552,6 +550,10 @@
         ctx = ctx_node->module->ctx;
     }
 
+    /* allocate return set */
+    ret = ly_set_new(set);
+    LY_CHECK_GOTO(ret, cleanup);
+
     /* compile expression */
     ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp);
     LY_CHECK_GOTO(ret, cleanup);
@@ -560,10 +562,6 @@
     ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options);
     LY_CHECK_GOTO(ret, cleanup);
 
-    /* allocate return set */
-    ret = ly_set_new(set);
-    LY_CHECK_GOTO(ret, cleanup);
-
     /* transform into ly_set */
     (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs);
     LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup);
diff --git a/src/xpath.c b/src/xpath.c
index 3184350..259fd9c 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -43,8 +43,6 @@
 #include "tree_schema_internal.h"
 #include "xml.h"
 
-static LY_ERR set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
-        enum lyxp_axis axis, uint32_t *index_p);
 static LY_ERR reparse_or_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth);
 static LY_ERR eval_expr_select(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_expr_type etype,
         struct lyxp_set *set, uint32_t options);
@@ -703,14 +701,14 @@
             assert(!r);
             (void)r;
 
-            if (hnode.node == node) {
+            if ((hnode.node == node) && (hnode.type == type)) {
                 /* it was just added, do not add it twice */
-                node = NULL;
+                return;
             }
         }
     }
 
-    if (set->ht && node) {
+    if (set->ht) {
         /* add the new node into hash table */
         hnode.node = node;
         hnode.type = type;
@@ -893,7 +891,7 @@
                     (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START)) {
                 uint32_t idx;
 
-                LY_CHECK_ERR_RET(set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type,
+                LY_CHECK_ERR_RET(lyxp_set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type,
                         set->val.scnodes[i].axis, &idx), lyxp_set_free(ret), NULL);
                 /* coverity seems to think scnodes can be NULL */
                 if (!ret->val.scnodes) {
@@ -993,11 +991,7 @@
         return;
     }
 
-    if (trg->type == LYXP_SET_NODE_SET) {
-        free(trg->val.nodes);
-    } else if (trg->type == LYXP_SET_STRING) {
-        free(trg->val.str);
-    }
+    lyxp_set_free_content(trg);
     set_init(trg, src);
 
     if (src->type == LYXP_SET_SCNODE_SET) {
@@ -1318,19 +1312,8 @@
     set_insert_node_hash(set, (struct lyd_node *)node, node_type);
 }
 
-/**
- * @brief Insert schema node into set.
- *
- * @param[in] set Set to insert into.
- * @param[in] node Node to insert.
- * @param[in] node_type Node type of @p node.
- * @param[in] axis Axis that @p node was reached on.
- * @param[out] index_p Optional pointer to store index if the inserted @p node.
- * @return LY_SUCCESS on success.
- * @return LY_EMEM on memory allocation failure.
- */
-static LY_ERR
-set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
+LY_ERR
+lyxp_set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
         enum lyxp_axis axis, uint32_t *index_p)
 {
     uint32_t index;
@@ -1350,7 +1333,7 @@
     }
 
     if (lyxp_set_scnode_contains(set, node, node_type, -1, &index)) {
-        /* BUG if axes differs, this new one is thrown away */
+        /* BUG if axes differ, this new one is thrown away */
         set->val.scnodes[index].in_ctx = LYXP_SET_SCNODE_ATOM_CTX;
     } else {
         if (set->used == set->size) {
@@ -3938,10 +3921,10 @@
         set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
 
         if (set->cur_scnode) {
-            LY_CHECK_RET(set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
+            LY_CHECK_RET(lyxp_set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
         } else {
             /* root node */
-            LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
+            LY_CHECK_RET(lyxp_set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
         }
     } else {
         lyxp_set_free_content(set);
@@ -4007,7 +3990,7 @@
                 target = p[LY_ARRAY_COUNT(p) - 1].node;
                 ly_path_free(set->ctx, p);
 
-                LY_CHECK_RET(set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
+                LY_CHECK_RET(lyxp_set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL));
             } /* else the target was found before but is disabled so it was removed */
         }
 
@@ -5661,7 +5644,7 @@
 
     if (options & LYXP_SCNODE_ALL) {
         set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE);
-        LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
+        LY_CHECK_RET(lyxp_set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL));
     } else {
         set->type = LYXP_SET_NODE_SET;
         set->used = 0;
@@ -6675,7 +6658,7 @@
 {
     ly_bool temp_ctx = 0;
     uint32_t getnext_opts, orig_used, i, mod_idx, idx;
-    const struct lys_module *mod;
+    const struct lys_module *mod = NULL;
     const struct lysc_node *iter;
     enum lyxp_node_type iter_type;
 
@@ -6714,7 +6697,7 @@
             }
 
             /* insert */
-            LY_CHECK_RET(set_scnode_insert_node(set, iter, iter_type, axis, &idx));
+            LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, iter_type, axis, &idx));
 
             /* we need to prevent these nodes from being considered in this moveto */
             if ((idx < orig_used) && (idx > i)) {
@@ -6727,7 +6710,7 @@
                 (set->val.scnodes[i].type == LYXP_NODE_ELEM) && !ly_nested_ext_schema(NULL, set->val.scnodes[i].scnode,
                 moveto_mod->name, strlen(moveto_mod->name), LY_VALUE_JSON, NULL, ncname, strlen(ncname), &iter, NULL)) {
             /* there is a matching node from an extension, use it */
-            LY_CHECK_RET(set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx));
+            LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx));
             if ((idx < orig_used) && (idx > i)) {
                 set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX;
                 temp_ctx = 1;
@@ -6871,7 +6854,7 @@
                     goto skip_children;
                 }
             } else {
-                LY_CHECK_RET(set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL));
+                LY_CHECK_RET(lyxp_set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL));
             }
         } else if (rc == LY_EINVAL) {
             goto skip_children;
@@ -9876,7 +9859,7 @@
     memset(set, 0, sizeof *set);
     set->type = LYXP_SET_SCNODE_SET;
     set->root_type = lyxp_get_root_type(NULL, ctx_scnode, options);
-    LY_CHECK_RET(set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL));
+    LY_CHECK_RET(lyxp_set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL));
     set->val.scnodes[0].in_ctx = LYXP_SET_SCNODE_START;
 
     set->ctx = (struct ly_ctx *)ctx;
diff --git a/src/xpath.h b/src/xpath.h
index cfa1fe6..96f7419 100644
--- a/src/xpath.h
+++ b/src/xpath.h
@@ -422,6 +422,20 @@
 void lyxp_set_scnode_merge(struct lyxp_set *set1, struct lyxp_set *set2);
 
 /**
+ * @brief Insert schema node into set.
+ *
+ * @param[in] set Set to insert into.
+ * @param[in] node Node to insert.
+ * @param[in] node_type Node type of @p node.
+ * @param[in] axis Axis that @p node was reached on.
+ * @param[out] index_p Optional pointer to store the index of the inserted @p node.
+ * @return LY_SUCCESS on success.
+ * @return LY_EMEM on memory allocation failure.
+ */
+LY_ERR lyxp_set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type,
+        enum lyxp_axis axis, uint32_t *index_p);
+
+/**
  * @brief Parse an XPath expression into a structure of tokens.
  *        Logs directly.
  *
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 6f36f31..259ef34 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -45,6 +45,8 @@
         add_subdirectory(style)
         add_subdirectory(fuzz)
     endif()
+    add_subdirectory(yanglint)
+    add_subdirectory(yangre)
 endif()
 if(ENABLE_PERF_TESTS)
     add_subdirectory(perf)
diff --git a/tests/tool_i.tcl b/tests/tool_i.tcl
new file mode 100644
index 0000000..d0f3d4b
--- /dev/null
+++ b/tests/tool_i.tcl
@@ -0,0 +1,156 @@
+# @brief Common functions and variables for Tool Under Test (TUT).
+#
+# The script requires variables:
+# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
+# TUT_NAME - TUT name (without path).
+#
+# The script sets the variables:
+# TUT - The path (including the name) of the executable TUT.
+# error_prompt - Delimiter on error.
+# error_head - Header on error.
+
+package require Expect
+
+# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release
+# subdirectory. Note that Release build takes precedence over Debug.
+set conftypes {{} Release Debug}
+foreach i $conftypes {
+    if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } {
+        set TUT "$TUT_PATH/$i/$TUT_NAME"
+        break
+    }
+}
+if {![info exists TUT]} {
+    error "$TUT_NAME executable not found"
+}
+
+# prompt of error message
+set error_prompt ">>>"
+# the beginning of error message
+set error_head "$error_prompt Check-failed"
+
+# detection on eof and timeout will be on every expect command
+expect_after {
+    eof {
+        global error_head
+        error "$error_head unexpected termination"
+    } timeout {
+        global error_head
+        error "$error_head timeout"
+    }
+}
+
+# Run commands from command line
+tcltest::loadTestedCommands
+
+# namespace of internal functions
+namespace eval ly::private {}
+
+# Send command 'cmd' to the process, then check output string by 'pattern'.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex or an exact string to match. If is not specified, only prompt assumed afterwards.
+# It must not contain a prompt. There can be an '$' character at the end of the pattern, in which case the regex
+# matches the characters before the prompt.
+# Parameter 'opt' can contain:
+#   -ex     has a similar meaning to the expect command. The 'pattern' parameter is used as a simple string
+#           for exact matching of the output. So 'pattern' is not a regular expression but some characters
+#           must still be escaped, eg ][.
+proc ly_cmd {cmd {pattern ""} {opt ""}} {
+    global prompt
+
+    send -- "${cmd}\r"
+    expect -- "${cmd}\r\n"
+
+    if { $pattern eq "" } {
+        # command without output
+        expect ^$prompt
+        return
+    }
+
+    # definition of an expression that matches failure
+    set failure_pattern "\r\n${prompt}$"
+
+    if { $opt eq "" && [string index $pattern end] eq "$"} {
+        # check output by regular expression
+        # It was explicitly specified how the expression should end.
+        set pattern [string replace $pattern end end]
+        expect {
+            -re "${pattern}\r\n${prompt}$" {}
+            -re $failure_pattern {
+                error "unexpected output:\n$expect_out(buffer)"
+            }
+        }
+    } elseif { $opt eq "" } {
+        # check output by regular expression
+        expect {
+            -re "${pattern}.*\r\n${prompt}$" {}
+            -re $failure_pattern {
+                error "unexpected output:\n$expect_out(buffer)"
+            }
+        }
+    } elseif { $opt eq "-ex" } {
+        # check output by exact matching
+        expect {
+            -ex "${pattern}\r\n${prompt}" {}
+            -re $failure_pattern {
+                error "unexpected output:\n$expect_out(buffer)"
+            }
+        }
+    } else {
+        global error_head
+        error "$error_head unrecognized value of parameter 'opt'"
+    }
+}
+
+# Send command 'cmd' to the process, expect some header and then check output string by 'pattern'.
+# This function is useful for checking an error that appears in the form of a header.
+# Parameter header is the expected header on the output.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex. It must not contain a prompt.
+proc ly_cmd_header {cmd header pattern} {
+    global prompt
+
+    send -- "${cmd}\r"
+    expect -- "${cmd}\r\n"
+
+    expect {
+        -re "$header .*${pattern}.*\r\n${prompt}$" {}
+        -re "\r\n${prompt}$" {
+            error "unexpected output:\n$expect_out(buffer)"
+        }
+    }
+}
+
+# Whatever is written is sent, output is ignored and then another prompt is expected.
+# Parameter cmd is optional and any output is ignored.
+proc ly_ignore {{cmd ""}} {
+    global prompt
+
+    send "${cmd}\r"
+    expect -re "$prompt$"
+}
+
+# Send a completion request and check if the anchored regex output matches.
+proc ly_completion {input output} {
+    global prompt
+
+    send -- "${input}\t"
+    # expecting echoing input, output and 10 terminal control characters
+    expect -re "^${input}\r${prompt}${output}.*\r.*$"
+}
+
+# Send a completion request and check if the anchored regex hint options match.
+proc ly_hint {input prev_input hints} {
+    global prompt
+
+    set output {}
+    foreach i $hints {
+        # each element might have some number of spaces and CRLF around it
+        append output "${i} *(?:\\r\\n)?"
+    }
+
+    send -- "${input}\t"
+    # expecting the hints, previous input from which the hints were generated
+    # and some number of terminal control characters
+    expect -re "${output}\r${prompt}${prev_input}.*\r.*$"
+}
diff --git a/tests/tool_ni.tcl b/tests/tool_ni.tcl
new file mode 100644
index 0000000..7282d35
--- /dev/null
+++ b/tests/tool_ni.tcl
@@ -0,0 +1,141 @@
+# @brief Common functions and variables for Tool Under Test (TUT).
+#
+# The script requires variables:
+# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
+# TUT_NAME - TUT name (without path).
+#
+# The script sets the variables:
+# TUT - The path (including the name) of the executable TUT.
+# error_prompt - Delimiter on error.
+# error_head - Header on error.
+
+# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release
+# subdirectory. Note that Release build takes precedence over Debug.
+set conftypes {{} Release Debug}
+foreach i $conftypes {
+    if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } {
+        set TUT "$TUT_PATH/$i/$TUT_NAME"
+        break
+    }
+}
+if {![info exists TUT]} {
+    error "$TUT_NAME executable not found"
+}
+
+# prompt of error message
+set error_prompt ">>>"
+# the beginning of error message
+set error_head "$error_prompt Check-failed"
+
+# Run commands from command line
+tcltest::loadTestedCommands
+
+# namespace of internal functions
+namespace eval ly::private {
+    namespace export *
+}
+
+# Run the process with arguments.
+# Parameter cmd is a string with arguments.
+# Parameter wrn is a flag. Set to 1 if stderr should be ignored.
+# Returns a pair where the first is the return code and the second is the output.
+proc ly::private::ly_exec {cmd {wrn ""}} {
+    global TUT
+    try {
+        set results [exec -- $TUT {*}$cmd]
+        set status 0
+    } trap CHILDSTATUS {results options} {
+        # return code is not 0
+        set status [lindex [dict get $options -errorcode] 2]
+    } trap NONE results {
+        if { $wrn == 1 } {
+            set status 0
+        } else {
+            error "return code is 0 but something was written to stderr:\n$results\n"
+        }
+    } trap CHILDKILLED {results options} {
+        set status [lindex [dict get $options -errorcode] 2]
+        error "process was killed: $status"
+    }
+    list $status $results
+}
+
+# Internal function.
+# Check the output with pattern.
+# Parameter pattern is a regex or an exact string to match.
+# Parameter msg is the output to check.
+# Parameter 'opt' is optional. If contains '-ex', then the 'pattern' parameter is
+# used as a simple string for exact matching of the output.
+proc ly::private::output_check {pattern msg {opt ""}} {
+    if { $opt eq "" } {
+        expr {![regexp -- $pattern $msg]}
+    } elseif { $opt eq "-ex" } {
+        expr {![string equal "$pattern" $msg]}
+    } else {
+        global error_head
+        error "$error_head unrecognized value of parameter 'opt'"
+    }
+}
+
+# Execute yanglint with arguments and expect success.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex or an exact string to match.
+# Parameter 'opt' is optional. If contains '-ex', then the 'pattern' parameter is
+# used as a simple string for exact matching of the output.
+proc ly_cmd {cmd {pattern ""} {opt ""}} {
+    namespace import ly::private::*
+    lassign [ly_exec $cmd] rc msg
+    if { $rc != 0 } {
+        error "unexpected return code $rc:\n$msg\n"
+    }
+    if { $pattern ne "" && [output_check $pattern $msg $opt] } {
+        error "unexpected output:\n$msg\n"
+    }
+    return
+}
+
+# Execute yanglint with arguments and expect error.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex.
+proc ly_cmd_err {cmd pattern} {
+    namespace import ly::private::*
+    lassign [ly_exec $cmd] rc msg
+    if { $rc == 0 } {
+        error "unexpected return code $rc"
+    }
+    if { [output_check $pattern $msg] } {
+        error "unexpected output:\n$msg\n"
+    }
+    return
+}
+
+# Execute yanglint with arguments, expect warning in stderr but success.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex.
+proc ly_cmd_wrn {cmd pattern} {
+    namespace import ly::private::*
+    lassign [ly_exec $cmd 1] rc msg
+    if { $rc != 0 } {
+        error "unexpected return code $rc:\n$msg\n"
+    }
+    if { [output_check $pattern $msg] } {
+        error "unexpected output:\n$msg\n"
+    }
+    return
+}
+
+# Check if yanglint supports the specified option.
+# Parameter opt is a option to be found.
+# Return true if option is found otherwise false.
+proc ly_opt_exists {opt} {
+    namespace import ly::private::*
+    lassign [ly_exec "--help"] rc msg
+    if { $rc != 0 } {
+        error "unexpected return code $rc:\n$msg\n"
+    }
+    if { [output_check $opt $msg] } {
+        return false
+    } else {
+        return true
+    }
+}
diff --git a/tests/utests/data/test_diff.c b/tests/utests/data/test_diff.c
index a84c3f6..4400b5d 100644
--- a/tests/utests/data/test_diff.c
+++ b/tests/utests/data/test_diff.c
@@ -4,7 +4,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief tests for lyd_diff()
  *
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -17,15 +17,15 @@
 
 #include "libyang.h"
 
-#define CHECK_PARSE_LYD(INPUT, MODEL) \
-        CHECK_PARSE_LYD_PARAM(INPUT, LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, MODEL)
+#define CHECK_PARSE_LYD(INPUT, OUTPUT) \
+        CHECK_PARSE_LYD_PARAM(INPUT, LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, OUTPUT)
 
-#define CHECK_LYD_STRING(IN_MODEL, TEXT) \
-        CHECK_LYD_STRING_PARAM(IN_MODEL, TEXT, LYD_XML, LYD_PRINT_WITHSIBLINGS)
+#define CHECK_LYD_STRING(INPUT, TEXT) \
+        CHECK_LYD_STRING_PARAM(INPUT, TEXT, LYD_XML, LYD_PRINT_WITHSIBLINGS)
 
-#define CHECK_PARSE_LYD_DIFF(INPUT_1, INPUT_2, OUT_MODEL) \
-        assert_int_equal(LY_SUCCESS, lyd_diff_siblings(INPUT_1, INPUT_2, 0, &OUT_MODEL));\
-        assert_non_null(OUT_MODEL)
+#define CHECK_PARSE_LYD_DIFF(INPUT_1, INPUT_2, OUT_DIFF) \
+        assert_int_equal(LY_SUCCESS, lyd_diff_siblings(INPUT_1, INPUT_2, 0, &OUT_DIFF));\
+        assert_non_null(OUT_DIFF)
 
 #define TEST_DIFF_3(XML1, XML2, XML3, DIFF1, DIFF2, MERGE) \
         { \
@@ -117,6 +117,12 @@
         "            leaf l2 {\n"
         "                type int32;\n"
         "            }\n"
+        ""
+        "            container cont {\n"
+        "                leaf l3 {\n"
+        "                    type string;\n"
+        "                }\n"
+        "            }\n"
         "        }\n"
         ""
         "        leaf-list dllist {\n"
@@ -639,6 +645,126 @@
 }
 
 static void
+test_nested_list(void **state)
+{
+    struct lyd_node *data1, *data2, *diff;
+    const char *xml1, *xml2;
+
+    (void) state;
+
+    xml1 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">"
+            "  <list>"
+            "    <name>n1</name>"
+            "    <value>25</value>"
+            "    <list2>"
+            "      <name2>n22</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "  </list>"
+            "  <list>"
+            "    <name>n2</name>"
+            "    <value>25</value>"
+            "    <list2>"
+            "      <name2>n22</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "  </list>"
+            "  <list>"
+            "    <name>n3</name>"
+            "    <value>25</value>"
+            "    <list2>"
+            "      <name2>n22</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "  </list>"
+            "  <list>"
+            "    <name>n4</name>"
+            "    <value>25</value>"
+            "    <list2>"
+            "      <name2>n22</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "  </list>"
+            "  <list>"
+            "    <name>n0</name>"
+            "    <value>26</value>"
+            "    <list2>"
+            "      <name2>n22</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "    <list2>"
+            "      <name2>n23</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "  </list>"
+            "</df>";
+    xml2 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">"
+            "  <list>"
+            "    <name>n0</name>"
+            "    <value>30</value>"
+            "    <list2>"
+            "      <name2>n23</name2>"
+            "      <value2>26</value2>"
+            "    </list2>"
+            "  </list>"
+            "</df>";
+
+    CHECK_PARSE_LYD(xml1, data1);
+    CHECK_PARSE_LYD(xml2, data2);
+    CHECK_PARSE_LYD_DIFF(data1, data2, diff);
+
+    CHECK_LYD_STRING(diff,
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <list yang:operation=\"delete\">\n"
+            "    <name>n1</name>\n"
+            "    <value>25</value>\n"
+            "    <list2>\n"
+            "      <name2>n22</name2>\n"
+            "      <value2>26</value2>\n"
+            "    </list2>\n"
+            "  </list>\n"
+            "  <list yang:operation=\"delete\">\n"
+            "    <name>n2</name>\n"
+            "    <value>25</value>\n"
+            "    <list2>\n"
+            "      <name2>n22</name2>\n"
+            "      <value2>26</value2>\n"
+            "    </list2>\n"
+            "  </list>\n"
+            "  <list yang:operation=\"delete\">\n"
+            "    <name>n3</name>\n"
+            "    <value>25</value>\n"
+            "    <list2>\n"
+            "      <name2>n22</name2>\n"
+            "      <value2>26</value2>\n"
+            "    </list2>\n"
+            "  </list>\n"
+            "  <list yang:operation=\"delete\">\n"
+            "    <name>n4</name>\n"
+            "    <value>25</value>\n"
+            "    <list2>\n"
+            "      <name2>n22</name2>\n"
+            "      <value2>26</value2>\n"
+            "    </list2>\n"
+            "  </list>\n"
+            "  <list yang:operation=\"none\">\n"
+            "    <name>n0</name>\n"
+            "    <value yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"26\">30</value>\n"
+            "    <list2 yang:operation=\"delete\">\n"
+            "      <name2>n22</name2>\n"
+            "      <value2>26</value2>\n"
+            "    </list2>\n"
+            "  </list>\n"
+            "</df>\n");
+
+    lyd_free_all(data1);
+    lyd_free_all(data2);
+    lyd_free_all(diff);
+}
+
+static void
 test_userord_llist(void **state)
 {
     (void) state;
@@ -942,6 +1068,118 @@
 }
 
 static void
+test_userord_list3(void **state)
+{
+    (void) state;
+    const char *xml1 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+            "  <ul>\n"
+            "    <l1>a</l1>\n"
+            "    <l2>1</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>b</l1>\n"
+            "    <l2>2</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>c</l1>\n"
+            "    <cont>\n"
+            "      <l3>val1</l3>\n"
+            "    </cont>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>d</l1>\n"
+            "    <l2>4</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *xml2 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+            "  <ul>\n"
+            "    <l1>c</l1>\n"
+            "    <l2>3</l2>\n"
+            "    <cont>\n"
+            "      <l3>val2</l3>\n"
+            "    </cont>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>a</l1>\n"
+            "    <l2>1</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>d</l1>\n"
+            "    <l2>44</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>b</l1>\n"
+            "    <l2>2</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *xml3 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+            "  <ul>\n"
+            "    <l1>a</l1>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>c</l1>\n"
+            "    <l2>3</l2>\n"
+            "    <cont>\n"
+            "      <l3>val2</l3>\n"
+            "    </cont>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>d</l1>\n"
+            "    <l2>44</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>b</l1>\n"
+            "    <l2>2</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+
+    const char *out_diff_1 =
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>c</l1>\n"
+            "    <l2 yang:operation=\"create\">3</l2>\n"
+            "    <cont yang:operation=\"none\">\n"
+            "      <l3 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"val1\">val2</l3>\n"
+            "    </cont>\n"
+            "  </ul>\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>d</l1>\n"
+            "    <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *out_diff_2 =
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='c']\">\n"
+            "    <l1>a</l1>\n"
+            "    <l2 yang:operation=\"delete\">1</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *out_merge =
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>c</l1>\n"
+            "    <l2 yang:operation=\"create\">3</l2>\n"
+            "    <cont yang:operation=\"none\">\n"
+            "      <l3 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"val1\">val2</l3>\n"
+            "    </cont>\n"
+            "  </ul>\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>d</l1>\n"
+            "    <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n"
+            "  </ul>\n"
+            "  <ul yang:key=\"\" yang:orig-key=\"[l1='c']\" yang:operation=\"replace\">\n"
+            "    <l1>a</l1>\n"
+            "    <l2 yang:operation=\"delete\">1</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+
+    TEST_DIFF_3(xml1, xml2, xml3, out_diff_1, out_diff_2, out_merge);
+}
+
+static void
 test_keyless_list(void **state)
 {
     (void) state;
@@ -1209,11 +1447,13 @@
         UTEST(test_delete_merge, setup),
         UTEST(test_leaf, setup),
         UTEST(test_list, setup),
+        UTEST(test_nested_list, setup),
         UTEST(test_userord_llist, setup),
         UTEST(test_userord_llist2, setup),
         UTEST(test_userord_mix, setup),
         UTEST(test_userord_list, setup),
         UTEST(test_userord_list2, setup),
+        UTEST(test_userord_list3, setup),
         UTEST(test_keyless_list, setup),
         UTEST(test_state_llist, setup),
         UTEST(test_wd, setup),
diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c
index d2c40e6..e5513da 100644
--- a/tests/utests/data/test_parser_json.c
+++ b/tests/utests/data/test_parser_json.c
@@ -45,6 +45,7 @@
             "leaf-list ll1 { type uint8; }"
             "leaf foo2 { type string; default \"default-val\"; }"
             "leaf foo3 { type uint32; }"
+            "leaf foo4 { type uint64; }"
             "notification n2;}";
 
     UTEST_SETUP;
@@ -520,6 +521,25 @@
     CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
     lyd_free_all(tree);
 
+    /* special chars */
+    data = "{\"a:foo3\":\"ab\\\"\\\\\\r\\t\"}";
+    CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+    CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
+    lyd_free_all(tree);
+
+    /* wrong encoding */
+    data = "{\"a:foo3\":\"25\"}";
+    CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_JSON, "foo3", 0, 0, NULL,  0,  "25");
+    CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
+    lyd_free_all(tree);
+
+    data = "{\"a:foo4\":25}";
+    CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree);
+    CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_JSON, "foo4", 0, 0, NULL,  0,  "25");
+    CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data);
+    lyd_free_all(tree);
+
     /* missing key, no flags */
     data = "{\"a:l1\":[{\"a\":\"val_a\",\"b\":\"val_b\",\"d\":\"val_d\"}]}";
     PARSER_CHECK_ERROR(data, 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID,
diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c
index 04397ae..01cf7f4 100644
--- a/tests/utests/data/test_parser_xml.c
+++ b/tests/utests/data/test_parser_xml.c
@@ -631,8 +631,33 @@
     lyd_free_all(tree);
     lyd_free_all(op);
 
-    /* wrong namespace, element name, whatever... */
-    /* TODO */
+    /* invalid anyxml nested metadata value */
+    data = "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"1\" pid=\"4114692032\">\n"
+            "  <copy-config>\n"
+            "    <target>\n"
+            "      <running/>\n"
+            "    </target>\n"
+            "    <source>\n"
+            "      <config>\n"
+            "        <l1 xmlns=\"urn:tests:a\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+            "          <a>val_a</a>\n"
+            "          <b>val_b</b>\n"
+            "          <c>5</c>\n"
+            "          <cont nc:operation=\"merge\">\n"
+            "            <e nc:operation=\"merge2\">false</e>\n"
+            "          </cont>\n"
+            "        </l1>\n"
+            "      </config>\n"
+            "    </source>\n"
+            "  </copy-config>\n"
+            "</rpc>\n";
+    assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in));
+    assert_int_equal(LY_EVALID, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_RPC_NETCONF, &tree, &op));
+    ly_in_free(in, 0);
+    CHECK_LOG_CTX("Invalid enumeration value \"merge2\".",
+            "Data location \"/ietf-netconf:copy-config/source/config/a:l1[a='val_a'][b='val_b'][c='5']/cont/e\", line number 13.");
+    lyd_free_all(tree);
+    assert_null(op);
 }
 
 static void
diff --git a/tests/utests/data/test_tree_data.c b/tests/utests/data/test_tree_data.c
index 7b17e50..462913b 100644
--- a/tests/utests/data/test_tree_data.c
+++ b/tests/utests/data/test_tree_data.c
@@ -1,9 +1,9 @@
 /**
  * @file test_tree_data.c
- * @author: Radek Krejci <rkrejci@cesnet.cz>
- * @brief unit tests for functions from tress_data.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for functions from tree_data.c
  *
- * Copyright (c) 2018-2019 CESNET, z.s.p.o.
+ * Copyright (c) 2018-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -100,7 +100,7 @@
     data2 = "<l2 xmlns=\"urn:tests:a\"><c><x>b</x></c></l2>";
     CHECK_PARSE_LYD(data1, 0, LYD_VALIDATE_PRESENT, tree1);
     CHECK_PARSE_LYD(data2, 0, LYD_VALIDATE_PRESENT, tree2);
-    assert_int_equal(LY_ENOT, lyd_compare_single(tree1->next, tree2->next, 0));
+    assert_int_equal(LY_ENOT, lyd_compare_single(tree1->next, tree2->next, LYD_COMPARE_FULL_RECURSION));
     assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1->next->next, tree2->next, 0));
     lyd_free_all(tree1);
     lyd_free_all(tree2);
@@ -371,7 +371,7 @@
     CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree1);
     assert_int_equal(LY_EINVAL, lyd_dup_single(((struct lyd_node_inner *)tree1)->child->prev,
             (struct lyd_node_inner *)tree1->next, LYD_DUP_WITH_PARENTS, NULL));
-    CHECK_LOG_CTX("Invalid argument parent (lyd_dup_get_local_parent()) - does not interconnect with the created node's parents chain.",
+    CHECK_LOG_CTX("None of the duplicated node \"c\" schema parents match the provided parent \"c\".",
             NULL);
     lyd_free_all(tree1);
 }
diff --git a/tests/utests/schema/test_printer_tree.c b/tests/utests/schema/test_printer_tree.c
index c076ece..40fb15f 100644
--- a/tests/utests/schema/test_printer_tree.c
+++ b/tests/utests/schema/test_printer_tree.c
@@ -1574,6 +1574,7 @@
             "  yang-version 1.1;\n"
             "  namespace \"x:y\";\n"
             "  prefix x;\n"
+            "\n"
             "  container g {\n"
             "    leaf a {\n"
             "      type string;\n"
@@ -1586,6 +1587,12 @@
             "      leaf c {\n"
             "        type string;\n"
             "      }\n"
+            "      list l {\n"
+            "        key \"ip\";\n"
+            "        leaf ip {\n"
+            "          type string;\n"
+            "        }\n"
+            "      }\n"
             "    }\n"
             "  }\n"
             "}\n";
@@ -1610,13 +1617,31 @@
 
     ly_out_reset(UTEST_OUT);
 
+    /* pyang -f tree --tree-path /g/h/l */
+    expect =
+            "module: a26\n"
+            "  +--rw g\n"
+            "     +--rw h\n"
+            "        +--rw l* [ip]\n"
+            "           +--rw ip    string\n";
+
+    node = lys_find_path(UTEST_LYCTX, NULL, "/a26:g/h/l", 0);
+    CHECK_POINTER(node, 1);
+    assert_int_equal(LY_SUCCESS, lys_print_node(UTEST_OUT, node, LYS_OUT_TREE, 72, 0));
+    assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT));
+    assert_string_equal(printed, expect);
+
+    ly_out_reset(UTEST_OUT);
+
     /* pyang -f tree --tree-path /g/h */
     expect =
             "module: a26\n"
             "  +--rw g\n"
             "     +--rw h\n"
             "        +--rw b    string\n"
-            "        +--rw c?   string\n";
+            "        +--rw c?   string\n"
+            "        +--rw l* [ip]\n"
+            "           +--rw ip    string\n";
 
     node = lys_find_path(UTEST_LYCTX, NULL, "/a26:g/h", 0);
     CHECK_POINTER(node, 1);
@@ -1643,6 +1668,59 @@
     TEST_LOCAL_TEARDOWN;
 }
 
+static void
+print_compiled_node_augment(void **state)
+{
+    TEST_LOCAL_SETUP;
+    const struct lysc_node *node;
+
+    orig =
+            "module b26xx {\n"
+            "  yang-version 1.1;\n"
+            "  namespace \"xx:y\";\n"
+            "  prefix xx;\n"
+            "  container c;\n"
+            "}\n";
+
+    UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
+
+    /* module with import statement */
+    orig =
+            "module b26 {\n"
+            "  yang-version 1.1;\n"
+            "  namespace \"x:y\";\n"
+            "  prefix x;\n"
+            "\n"
+            "  import b26xx {\n"
+            "    prefix xx;\n"
+            "  }\n"
+            "\n"
+            "  augment \"/xx:c\" {\n"
+            "    container e;\n"
+            "  }\n"
+            "}\n";
+
+    UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
+
+    /* pyang -f tree --tree-path /c/e ... but prefixes modified */
+    expect =
+            "module: b26\n"
+            "  +--rw xx:c\n"
+            "     +--rw e\n";
+
+    /* using lysc tree */
+    ly_ctx_set_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED);
+    node = lys_find_path(UTEST_LYCTX, NULL, "/b26xx:c/b26:e", 0);
+    CHECK_POINTER(node, 1);
+    assert_int_equal(LY_SUCCESS, lys_print_node(UTEST_OUT, node, LYS_OUT_TREE, 72, 0));
+    assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT));
+    assert_string_equal(printed, expect);
+    ly_out_reset(UTEST_OUT);
+    ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED);
+
+    TEST_LOCAL_TEARDOWN;
+}
+
 static LY_ERR
 local_imp_clb(const char *UNUSED(mod_name), const char *UNUSED(mod_rev), const char *UNUSED(submod_name),
         const char *UNUSED(sub_rev), void *user_data, LYS_INFORMAT *format,
@@ -2365,6 +2443,43 @@
     TEST_LOCAL_TEARDOWN;
 }
 
+static void
+annotation(void **state)
+{
+    TEST_LOCAL_SETUP;
+
+    orig =
+            "module ann {\n"
+            "  yang-version 1.1;\n"
+            "  namespace \"urn:example:ann\";\n"
+            "  prefix an;\n"
+            "\n"
+            "  import ietf-yang-metadata {\n"
+            "    prefix md;\n"
+            "  }\n"
+            "\n"
+            "  leaf lf1 {\n"
+            "    type string;\n"
+            "  }\n"
+            "  md:annotation avalue {\n"
+            "    type string;\n"
+            "  }\n"
+            "}\n";
+
+    expect =
+            "module: ann\n"
+            "  +--rw lf1?   string\n";
+
+    /* annotation is ignored without error message */
+    UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod);
+    TEST_LOCAL_PRINT(mod, 72);
+    assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT));
+    assert_string_equal(printed, expect);
+    ly_out_reset(UTEST_OUT);
+
+    TEST_LOCAL_TEARDOWN;
+}
+
 int
 main(void)
 {
@@ -2395,10 +2510,12 @@
         UTEST(transition_between_rpc_and_notif),
         UTEST(local_augment),
         UTEST(print_compiled_node),
+        UTEST(print_compiled_node_augment),
         UTEST(print_parsed_submodule),
         UTEST(yang_data),
         UTEST(mount_point),
         UTEST(structure),
+        UTEST(annotation),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/schema/test_tree_schema_compile.c b/tests/utests/schema/test_tree_schema_compile.c
index 09c63a3..077faf0 100644
--- a/tests/utests/schema/test_tree_schema_compile.c
+++ b/tests/utests/schema/test_tree_schema_compile.c
@@ -454,7 +454,7 @@
 
     assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm {namespace urn:mm;prefix mm;"
             "list l {key x;leaf x {type empty;}}}", LYS_IN_YANG, NULL));
-    CHECK_LOG_CTX("List's key cannot be of \"empty\" type until it is in YANG 1.1 module.", "Path \"/mm:l/x\".");
+    CHECK_LOG_CTX("List key of the \"empty\" type is allowed only in YANG 1.1 modules.", "Path \"/mm:l/x\".");
 }
 
 static void
@@ -3901,6 +3901,55 @@
             "  }"
             "}",
             LYS_IN_YANG, NULL));
+
+    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb,
+            "module d1 {"
+            "  namespace urn:d1;"
+            "  prefix d1;"
+            "  container ifm {"
+            "    container interfaces {"
+            "      list interface {"
+            "        key \"name\";"
+            "        leaf name {"
+            "          type string;"
+            "        }"
+            "        container ethernet {"
+            "          container main-interface {"
+            "            container l2-attribute {"
+            "              when \"not(/d1:ifm/d1:interfaces/d1:interface/d1:trunk/d1:members/d1:member[d1:name=current()/../../../d1:name])\";"
+            "              presence \"\";"
+            "            }"
+            "          }"
+            "        }"
+            "        container trunk {"
+            "          container members {"
+            "            list member {"
+            "              key \"name\";"
+            "              leaf name {"
+            "                type string;"
+            "              }"
+            "            }"
+            "          }"
+            "        }"
+            "      }"
+            "    }"
+            "  }"
+            "}");
+    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX,
+            "module d2 {"
+            "  namespace \"urn:d2\";"
+            "  prefix d2;"
+            "  import d1 {"
+            "    prefix d1;"
+            "  }"
+            "  augment \"/d1:ifm/d1:interfaces/d1:interface/d1:ethernet/d1:main-interface\" {"
+            "    when \"not(d1:l2-attribute)\";"
+            "    container extra-attribute {"
+            "      presence \"\";"
+            "    }"
+            "  }"
+            "}",
+            LYS_IN_YANG, NULL));
 }
 
 static void
@@ -3942,6 +3991,68 @@
             LYS_IN_YANG, NULL));
     /* no warnings */
     CHECK_LOG_CTX(NULL, NULL);
+
+    /* must referencing disabled leafref in another module */
+    ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb,
+            "module b-imp {"
+            "  yang-version 1.1;"
+            "  namespace \"urn:b-imp\";"
+            "  prefix \"bi\";"
+            ""
+            "  feature feat;"
+            ""
+            "  grouping band-capabilities {"
+            "    leaf band-number {"
+            "      type uint16;"
+            "    }"
+            ""
+            "    container sub-band-info {"
+            "      when \"../band-number = '46'\";"
+            "      if-feature \"bi:feat\";"
+            "      leaf number-of-laa-scarriers {"
+            "        type uint8;"
+            "      }"
+            "    }"
+            "  }"
+            ""
+            "  container module-capability {"
+            "    list band-capabilities {"
+            "      key band-number;"
+            "      config false;"
+            "      uses band-capabilities;"
+            "    }"
+            "    container rw-sub-band-info {"
+            "      if-feature \"bi:feat\";"
+            "      leaf rw-number-of-laa-scarriers {"
+            "        type leafref {"
+            "          path \"/module-capability/band-capabilities/sub-band-info/number-of-laa-scarriers\";"
+            "          require-instance false;"
+            "        }"
+            "      }"
+            "    }"
+            "  }"
+            "}");
+
+    ly_ctx_set_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED);
+    assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX,
+            "module b {"
+            "  yang-version 1.1;"
+            "  namespace \"urn:b\";"
+            "  prefix \"b\";"
+            ""
+            "  import b-imp {"
+            "    prefix \"bi\";"
+            "  }"
+            ""
+            "  container laa-config {"
+            "    must \"number-of-laa-scarriers <= /bi:module-capability/bi:rw-sub-band-info/bi:rw-number-of-laa-scarriers\";"
+            "  }"
+            "}",
+            LYS_IN_YANG, NULL));
+    ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED);
+
+    CHECK_LOG_CTX("Schema node \"number-of-laa-scarriers\" not found; in expr \"number-of-laa-scarriers\" "
+            "with context node \"/b:laa-config\".", NULL);
 }
 
 int
diff --git a/tests/utests/types/identityref.c b/tests/utests/types/identityref.c
index cdfe057..b69299e 100644
--- a/tests/utests/types/identityref.c
+++ b/tests/utests/types/identityref.c
@@ -27,15 +27,6 @@
     NODES \
     "}\n"
 
-#define TEST_SUCCESS_XML(MOD_NAME, NAMESPACES, NODE_NAME, DATA, TYPE, ...) \
-    { \
-        struct lyd_node *tree; \
-        const char *data = "<" NODE_NAME " xmlns=\"urn:tests:" MOD_NAME "\" " NAMESPACES ">" DATA "</" NODE_NAME ">"; \
-        CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); \
-        CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, TYPE, __VA_ARGS__); \
-        lyd_free_all(tree); \
-    }
-
 #define TEST_ERROR_XML(MOD_NAME, NAMESPACES, NODE_NAME, DATA) \
     {\
         struct lyd_node *tree; \
@@ -64,21 +55,44 @@
 test_data_xml(void **state)
 {
     const char *schema, *schema2;
+    struct lyd_node *tree;
+    const char *data;
 
     /* xml test */
-    schema = MODULE_CREATE_YANG("ident-base", "identity ident-base;"
-            "identity ident-imp {base ident-base;}");
+    schema = "module ident-base {"
+            "  yang-version 1.1;"
+            "  namespace \"urn:tests:ident-base\";"
+            "  prefix ib;"
+            "  identity ident-base;"
+            "  identity ident-imp {base ident-base;}"
+            "}";
     UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
 
-    schema2 = MODULE_CREATE_YANG("defs", "import ident-base {prefix ib;}"
-            "identity ident1 {base ib:ident-base;}"
-            "leaf l1 {type identityref {base ib:ident-base;}}");
+    schema2 = "module defs {"
+            "  yang-version 1.1;"
+            "  namespace \"urn:tests:defs\";"
+            "  prefix d;"
+            "  import ident-base {prefix ib;}"
+            "  identity ident1 {base ib:ident-base;}"
+            "  leaf l1 {type identityref {base ib:ident-base;}}"
+            "}";
     UTEST_ADD_MODULE(schema2, LYS_IN_YANG, NULL, NULL);
 
-    TEST_SUCCESS_XML("defs", "", "l1", "ident1", IDENT, "defs:ident1", "ident1");
+    /* local ident, XML/JSON print */
+    data = "<l1 xmlns=\"urn:tests:defs\">ident1</l1>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, IDENT, "ident1", "ident1");
+    CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, LYD_PRINT_SHRINK);
+    CHECK_LYD_STRING_PARAM(tree, "{\"defs:l1\":\"ident1\"}", LYD_JSON, LYD_PRINT_SHRINK);
+    lyd_free_all(tree);
 
-    TEST_SUCCESS_XML("defs", "xmlns:i=\"urn:tests:ident-base\"", "l1", "i:ident-imp", IDENT, "ident-base:ident-imp",
-            "ident-imp");
+    /* foreign ident, XML/JSON print */
+    data = "<l1 xmlns=\"urn:tests:defs\" xmlns:ib=\"urn:tests:ident-base\">ib:ident-imp</l1>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, IDENT, "ident-base:ident-imp", "ident-imp");
+    CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, LYD_PRINT_SHRINK);
+    CHECK_LYD_STRING_PARAM(tree, "{\"defs:l1\":\"ident-base:ident-imp\"}", LYD_JSON, LYD_PRINT_SHRINK);
+    lyd_free_all(tree);
 
     /* invalid value */
     TEST_ERROR_XML("defs", "", "l1", "fast-ethernet");
diff --git a/tests/utests/types/instanceid.c b/tests/utests/types/instanceid.c
index 4174e4d..efc2b44 100644
--- a/tests/utests/types/instanceid.c
+++ b/tests/utests/types/instanceid.c
@@ -125,7 +125,7 @@
             "<list-ident xmlns=\"urn:tests:defs\"><id xmlns:b=\"urn:tests:defs\">b:ident-der2</id>"
             "<value>y</value></list-ident>",
             "defs", "xmlns:a=\"urn:tests:defs\"", "a:l1", "/a:list-ident[a:id='a:ident-der1']/a:value",
-            INST, "/defs:list-ident[id='defs:ident-der1']/value", val5);
+            INST, "/defs:list-ident[id='ident-der1']/value", val5);
 
     TEST_SUCCESS_XML2("<list2 xmlns=\"urn:tests:defs\"><id>defs:xxx</id><id2>x</id2></list2>"
             "<list2 xmlns=\"urn:tests:defs\"><id>a:xxx</id><id2>y</id2></list2>",
diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c
index c8d0cb6..7005d08 100644
--- a/tests/utests/types/leafref.c
+++ b/tests/utests/types/leafref.c
@@ -209,6 +209,38 @@
     TEST_SUCCESS_LYB("lyb", "lst", "key_str", "lref", "key_str");
 }
 
+static void
+test_data_xpath_json(void **state)
+{
+    const char *schema, *data;
+    struct lyd_node *tree;
+
+    ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED);
+
+    /* json xpath test */
+    schema = MODULE_CREATE_YANG("xp_test",
+            "list l1 {key t1;"
+            "leaf t1 {type uint8;}"
+            "list l2 {key t2;"
+            "leaf t2 {type uint8;}"
+            "leaf-list l3 {type uint8;}"
+            "}}"
+            "leaf r1 {type leafref {path \"../l1/t1\";}}"
+            "leaf r2 {type leafref {path \"deref(../r1)/../l2/t2\";}}"
+            "leaf r3 {type leafref {path \"deref(../r2)/../l3\";}}");
+
+    UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
+
+    data = "{"
+            "  \"xp_test:l1\":[{\"t1\": 1,\"l2\":[{\"t2\": 2,\"l3\":[3]}]}],"
+            "  \"xp_test:r1\": 1,"
+            "  \"xp_test:r2\": 2,"
+            "  \"xp_test:r3\": 3"
+            "}";
+    CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    lyd_free_all(tree);
+}
+
 int
 main(void)
 {
@@ -216,6 +248,7 @@
         UTEST(test_data_xml),
         UTEST(test_data_json),
         UTEST(test_plugin_lyb),
+        UTEST(test_data_xpath_json),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/types/union.c b/tests/utests/types/union.c
index 3522795..c4c282c 100644
--- a/tests/utests/types/union.c
+++ b/tests/utests/types/union.c
@@ -86,7 +86,7 @@
             "defs", "", "un1", "2", UNION, "2", STRING, "2");
 
     TEST_SUCCESS_XML2("<int8 xmlns=\"urn:tests:defs\">10</int8>",
-            "defs", "xmlns:x=\"urn:tests:defs\"", "un1", "x:ident2", UNION, "defs:ident2", IDENT, "defs:ident2", "ident2");
+            "defs", "xmlns:x=\"urn:tests:defs\"", "un1", "x:ident2", UNION, "ident2", IDENT, "ident2", "ident2");
 
     TEST_SUCCESS_XML2("<int8 xmlns=\"urn:tests:defs\">10</int8>",
             "defs", "xmlns:x=\"urn:tests:defs\"", "un1", "x:ident55", UNION, "x:ident55", STRING, "x:ident55");
diff --git a/tests/yanglint/CMakeLists.txt b/tests/yanglint/CMakeLists.txt
new file mode 100644
index 0000000..c1e081a
--- /dev/null
+++ b/tests/yanglint/CMakeLists.txt
@@ -0,0 +1,36 @@
+if(WIN32)
+    set(YANGLINT_INTERACTIVE OFF)
+else()
+    set(YANGLINT_INTERACTIVE ON)
+endif()
+
+function(add_yanglint_test)
+    cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN})
+    set(TEST_NAME yanglint_${ADDTEST_NAME})
+
+    if(${ADDTEST_VIA} STREQUAL "tclsh")
+        set(WRAPPER ${PATH_TCLSH})
+    else()
+        message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'")
+    endif()
+
+    add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/${ADDTEST_SCRIPT})
+    set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "TESTS_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
+    set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/modules")
+    set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}")
+endfunction(add_yanglint_test)
+
+if(ENABLE_TESTS)
+    # tests of interactive mode using tclsh
+    find_program(PATH_TCLSH NAMES tclsh)
+    if(NOT PATH_TCLSH)
+        message(WARNING "'tclsh' not found! The yanglint(1) interactive tests will not be available.")
+    else()
+        if(YANGLINT_INTERACTIVE)
+            add_yanglint_test(NAME interactive VIA tclsh SCRIPT interactive/all.tcl)
+            add_yanglint_test(NAME non-interactive VIA tclsh SCRIPT non-interactive/all.tcl)
+        else()
+            add_yanglint_test(NAME non-interactive VIA tclsh SCRIPT non-interactive/all.tcl)
+        endif()
+    endif()
+endif()
diff --git a/tests/yanglint/README.md b/tests/yanglint/README.md
new file mode 100644
index 0000000..6c51d89
--- /dev/null
+++ b/tests/yanglint/README.md
@@ -0,0 +1,107 @@
+# yanglint testing
+
+Testing yanglint is divided into two ways.
+It is either tested in interactive mode using the tcl command 'expect' or non-interactively, classically from the command line.
+For both modes, unit testing was used using the tcl package tcltest.
+
+## How to
+
+The sample commands in this chapter using `tclsh` are called in the `interactive` or `non-interactive` directories.
+
+### How to run all yanglint tests?
+
+In the build directory designated for cmake, enter:
+
+```
+ctest -R yanglint
+```
+
+### How to run all yanglint tests that are in interactive mode?
+
+In the interactive directory, run:
+
+```
+tclsh all.tcl
+```
+
+### How to run all yanglint tests that are in non-interactive mode?
+
+In the non-interactive directory, run:
+
+```
+tclsh all.tcl
+```
+
+### How to run all unit-tests from .test file?
+
+```
+tclsh clear.test
+```
+
+or alternatively:
+
+```
+tclsh all.tcl -file clear.test
+```
+
+### How to run one unit-test?
+
+```
+tclsh clear.test -match clear_ietf_yang_library
+```
+
+or alternatively:
+
+```
+tclsh all.tcl -file clear.test -match clear_ietf_yang_library
+```
+
+### How to run unit-tests for a certain yanglint command?
+
+Test names are assumed to consist of the command name:
+
+```
+tclsh all.tcl -match clear*
+```
+
+### How do I get more detailed information about 'expect' for a certain test?
+
+In the interactive directory, run:
+
+```
+tclsh clear.test -match clear_ietf_yang_library -load "exp_internal 1"
+```
+
+### How do I get more detailed dialog between 'expect' and yanglint for a certain test?
+
+In the interactive directory, run:
+
+```
+tclsh clear.test -match clear_ietf_yang_library -load "log_user 1"
+```
+
+### How do I suppress error message from tcltest?
+
+Probably only possible to do via `-verbose ""`
+
+### How can I also debug?
+
+You can write commands `interact` and `interpreter` from 'Expect' package into some test.
+However, the most useful are the `exp_internal` and `log_user`, which can also be written directly into the test.
+See also the rlwrap tool.
+You can also use other debugging methods used in tcl programming.
+
+### Are the tests between interactive mode and non-interactive mode similar?
+
+Sort of...
+- regex \n must be changed to \r\n in the tests for interactive yanglint
+
+### I would like to add a new "ly_" function.
+
+Add it to the ly.tcl file.
+If you need to call other subfunctions in it, add them to namespace ly::private.
+
+### I would like to use function other than those prefixed with "ly_".
+
+Look in the common.tcl file in the "uti" namespace,
+which contains general tcl functions that can be used in both interactive and non-interactive tests.
diff --git a/tests/yanglint/common.tcl b/tests/yanglint/common.tcl
new file mode 100644
index 0000000..d186282
--- /dev/null
+++ b/tests/yanglint/common.tcl
@@ -0,0 +1,114 @@
+# @brief Common functions and variables for yanglint-interactive and yanglint-non-interactive.
+#
+# The script requires variables:
+# ::env(TESTS_DIR) - Main test directory. Must be set if the script is run via ctest.
+#
+# The script sets the variables:
+# ::env(TESTS_DIR) - Main test directory. It is set by default if not defined.
+# ::env(YANG_MODULES_DIR) - Directory of YANG modules.
+# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
+# TUT_NAME - TUT name (without path).
+# ::tcltest::testConstraint ctest - A tcltest variable that is set to true if the script is run via ctest. Causes tests
+#   to be a skipped.
+
+package require tcltest
+namespace import ::tcltest::test ::tcltest::cleanupTests
+
+# Set directory paths for testing yanglint.
+if { ![info exists ::env(TESTS_DIR)] } {
+    # the script is not run via 'ctest' so paths must be set
+    set ::env(TESTS_DIR) "../"
+    set ::env(YANG_MODULES_DIR) "../modules"
+    set TUT_PATH "../../../build"
+    ::tcltest::testConstraint ctest false
+} else {
+    # cmake (ctest) already sets ::env variables
+    set TUT_PATH $::env(YANGLINT)
+    ::tcltest::testConstraint ctest true
+}
+
+set TUT_NAME "yanglint"
+
+# The script continues by defining functions specific to the yanglint tool.
+
+namespace eval uti {
+    namespace export *
+}
+
+# Iterate through the items in the list 'lst' and return a new list where
+# the items will have the form: <prefix><item><suffix>.
+# Parameter 'index' determines at which index it will start wrapping.
+# Parameter 'step' specifies how far the iterator must move to wrap the next item.
+proc uti::wrap_list_items {lst {prefix ""} {suffix ""} {index 0} {step 1}} {
+    # counter to track when to insert wrapper
+    set cnt $step
+    set len [llength $lst]
+
+    if {$index > 0} {
+        # copy list from interval <0;$index)
+        set ret [lrange $lst 0 [expr {$index - 1}]]
+    } else {
+        set ret {}
+    }
+
+    for {set i $index} {$i < $len} {incr i} {
+        incr cnt
+        set item [lindex $lst $i]
+        if {$cnt >= $step} {
+            # insert wrapper for item
+            set cnt 0
+            lappend ret [string cat $prefix $item $suffix]
+        } else {
+            # just copy item
+            lappend ret $item
+        }
+    }
+
+    return $ret
+}
+
+# Wrap list items with xml tags.
+# The element format is: <tag>value</tag>
+# Parameter 'values' is list of values.
+# Parameter 'tag' is the name of the searched tag.
+proc uti::wrap_to_xml {values tag {index 0} {step 1}} {
+    return [wrap_list_items $values "<$tag>" "</$tag>" $index $step]
+}
+
+# Wrap list items with json attributes.
+# The pair format is: "attribute": "value"
+# Parameter 'values' is list of values.
+# Parameter 'attribute' is the name of the searched attribute.
+proc uti::wrap_to_json {values attribute {index 0} {step 1}} {
+    return [wrap_list_items $values "\"$attribute\": \"" "\"" $index $step]
+}
+
+# Convert list to a regex (which is just a string) so that 'delim' is between items,
+# 'begin' is at the beginning of the expression and 'end' is at the end.
+proc uti::list_to_regex {lst {delim ".*"} {begin ".*"} {end ".*"}} {
+    return [string cat $begin [join $lst $delim] $end]
+}
+
+# Merge two lists into one such that the nth items are merged into one separated by a delimiter.
+# Returns a list that is the same length as 'lst1' and 'lst2'
+proc uti::blend_lists {lst1 lst2 {delim ".*"}} {
+    return [lmap a $lst1 b $lst2 {string cat $a $delim $b}]
+}
+
+# Create regex to find xml elements.
+# The element format is: <tag>value</tag>
+# Parameter 'values' is list of values.
+# Parameter 'tag' is the name of the searched tag.
+# The resulting expression looks like: ".*<tag>value1</tag>.*<tag>value2</tag>.*..."
+proc uti::regex_xml_elements {values tag} {
+    return [list_to_regex [wrap_to_xml $values $tag]]
+}
+
+# Create regex to find json pairs.
+# The pair format is: "attribute": "value"
+# Parameter 'values' is list of values.
+# Parameter 'attribute' is the name of the searched attribute.
+# The resulting expression looks like: ".*\"attribute\": \"value1\".*\"attribute\": \"value2\".*..."
+proc uti::regex_json_pairs {values attribute} {
+    return [list_to_regex [wrap_to_json $values $attribute]]
+}
diff --git a/tests/yanglint/data/modaction.xml b/tests/yanglint/data/modaction.xml
new file mode 100644
index 0000000..37faa2d
--- /dev/null
+++ b/tests/yanglint/data/modaction.xml
@@ -0,0 +1,8 @@
+<con xmlns="urn:yanglint:modaction">
+  <ls>
+    <lfkey>kv</lfkey>
+    <act>
+      <lfi>some_input</lfi>
+    </act>
+  </ls>
+</con>
diff --git a/tests/yanglint/data/modaction_ds.xml b/tests/yanglint/data/modaction_ds.xml
new file mode 100644
index 0000000..a5a1727
--- /dev/null
+++ b/tests/yanglint/data/modaction_ds.xml
@@ -0,0 +1,5 @@
+<con xmlns="urn:yanglint:modaction">
+  <ls>
+    <lfkey>kv</lfkey>
+  </ls>
+</con>
diff --git a/tests/yanglint/data/modaction_nc.xml b/tests/yanglint/data/modaction_nc.xml
new file mode 100644
index 0000000..a74b6bf
--- /dev/null
+++ b/tests/yanglint/data/modaction_nc.xml
@@ -0,0 +1,13 @@
+<rpc message-id="101"
+     xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <action xmlns="urn:ietf:params:xml:ns:yang:1">
+    <con xmlns="urn:yanglint:modaction">
+      <ls>
+        <lfkey>kv</lfkey>
+        <act>
+          <lfi>some_input</lfi>
+        </act>
+      </ls>
+    </con>
+  </action>
+</rpc>
diff --git a/tests/yanglint/data/modaction_reply.xml b/tests/yanglint/data/modaction_reply.xml
new file mode 100644
index 0000000..7d6532d
--- /dev/null
+++ b/tests/yanglint/data/modaction_reply.xml
@@ -0,0 +1,8 @@
+<con xmlns="urn:yanglint:modaction">
+  <ls>
+    <lfkey>kv</lfkey>
+    <act>
+      <lfo>-56</lfo>
+    </act>
+  </ls>
+</con>
diff --git a/tests/yanglint/data/modaction_reply_nc.xml b/tests/yanglint/data/modaction_reply_nc.xml
new file mode 100644
index 0000000..f7c3b8f
--- /dev/null
+++ b/tests/yanglint/data/modaction_reply_nc.xml
@@ -0,0 +1,4 @@
+<rpc-reply message-id="101"
+     xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <lfo xmlns="urn:yanglint:modaction">-56</lfo>
+</rpc-reply>
diff --git a/tests/yanglint/data/modconfig.xml b/tests/yanglint/data/modconfig.xml
new file mode 100644
index 0000000..f8a03a9
--- /dev/null
+++ b/tests/yanglint/data/modconfig.xml
@@ -0,0 +1,4 @@
+<mcc xmlns="urn:yanglint:modconfig">
+  <lft>rw</lft>
+  <lff>ro</lff>
+</mcc>
diff --git a/tests/yanglint/data/modconfig2.xml b/tests/yanglint/data/modconfig2.xml
new file mode 100644
index 0000000..c96e344
--- /dev/null
+++ b/tests/yanglint/data/modconfig2.xml
@@ -0,0 +1,3 @@
+<mcc xmlns="urn:yanglint:modconfig">
+  <lft>rw</lft>
+</mcc>
diff --git a/tests/yanglint/data/modconfig_ctx.xml b/tests/yanglint/data/modconfig_ctx.xml
new file mode 100644
index 0000000..124989c
--- /dev/null
+++ b/tests/yanglint/data/modconfig_ctx.xml
@@ -0,0 +1,13 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set>
+    <name>main-set</name>
+    <module>
+      <name>modconfig</name>
+      <namespace>urn:yanglint:modconfig</namespace>
+    </module>
+  </module-set>
+  <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set-id>1</module-set-id>
+</modules-state>
diff --git a/tests/yanglint/data/moddatanodes.xml b/tests/yanglint/data/moddatanodes.xml
new file mode 100644
index 0000000..8ae6e97
--- /dev/null
+++ b/tests/yanglint/data/moddatanodes.xml
@@ -0,0 +1,17 @@
+<dnc xmlns="urn:yanglint:moddatanodes">
+  <lf>x</lf>
+  <lfl>1</lfl>
+  <lfl>2</lfl>
+  <con>
+    <lt>
+      <kalf>ka1</kalf>
+      <kblf>kb1</kblf>
+      <vlf>v1</vlf>
+    </lt>
+    <lt>
+      <kalf>ka2</kalf>
+      <kblf>kb2</kblf>
+      <vlf>v2</vlf>
+    </lt>
+  </con>
+</dnc>
diff --git a/tests/yanglint/data/moddefault.xml b/tests/yanglint/data/moddefault.xml
new file mode 100644
index 0000000..00f3a9d
--- /dev/null
+++ b/tests/yanglint/data/moddefault.xml
@@ -0,0 +1,4 @@
+<mdc xmlns="urn:yanglint:moddefault">
+  <lf>0</lf>
+  <di>5</di>
+</mdc>
diff --git a/tests/yanglint/data/modimp_type_ctx.xml b/tests/yanglint/data/modimp_type_ctx.xml
new file mode 100644
index 0000000..e6d158a
--- /dev/null
+++ b/tests/yanglint/data/modimp_type_ctx.xml
@@ -0,0 +1,13 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set>
+    <name>main-set</name>
+    <module>
+      <name>modimp-type</name>
+      <namespace>urn:yanglint:modimp-type</namespace>
+    </module>
+  </module-set>
+  <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set-id>1</module-set-id>
+</modules-state>
diff --git a/tests/yanglint/data/modleaf.djson b/tests/yanglint/data/modleaf.djson
new file mode 100644
index 0000000..25af218
--- /dev/null
+++ b/tests/yanglint/data/modleaf.djson
@@ -0,0 +1,3 @@
+{
+  "modleaf:lfl": 7
+}
diff --git a/tests/yanglint/data/modleaf.dxml b/tests/yanglint/data/modleaf.dxml
new file mode 100644
index 0000000..408936a
--- /dev/null
+++ b/tests/yanglint/data/modleaf.dxml
@@ -0,0 +1 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
diff --git a/tests/yanglint/data/modleaf.xml b/tests/yanglint/data/modleaf.xml
new file mode 100644
index 0000000..408936a
--- /dev/null
+++ b/tests/yanglint/data/modleaf.xml
@@ -0,0 +1 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
diff --git a/tests/yanglint/data/modleafref.xml b/tests/yanglint/data/modleafref.xml
new file mode 100644
index 0000000..c9fb147
--- /dev/null
+++ b/tests/yanglint/data/modleafref.xml
@@ -0,0 +1,2 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
+<lfr xmlns="urn:yanglint:modleafref">7</lfr>
diff --git a/tests/yanglint/data/modleafref2.xml b/tests/yanglint/data/modleafref2.xml
new file mode 100644
index 0000000..3946daf
--- /dev/null
+++ b/tests/yanglint/data/modleafref2.xml
@@ -0,0 +1,2 @@
+<lfl xmlns="urn:yanglint:modleaf">7</lfl>
+<lfr xmlns="urn:yanglint:modleafref">10</lfr>
diff --git a/tests/yanglint/data/modmandatory.xml b/tests/yanglint/data/modmandatory.xml
new file mode 100644
index 0000000..108cb2a
--- /dev/null
+++ b/tests/yanglint/data/modmandatory.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmandatory">
+  <lft>9</lft>
+</mmc>
diff --git a/tests/yanglint/data/modmandatory_invalid.xml b/tests/yanglint/data/modmandatory_invalid.xml
new file mode 100644
index 0000000..de71895
--- /dev/null
+++ b/tests/yanglint/data/modmandatory_invalid.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmandatory">
+  <lff>9</lff>
+</mmc>
diff --git a/tests/yanglint/data/modmerge.xml b/tests/yanglint/data/modmerge.xml
new file mode 100644
index 0000000..b52eff5
--- /dev/null
+++ b/tests/yanglint/data/modmerge.xml
@@ -0,0 +1,4 @@
+<mmc xmlns="urn:yanglint:modmerge">
+  <en>one</en>
+  <lm>4</lm>
+</mmc>
diff --git a/tests/yanglint/data/modmerge2.xml b/tests/yanglint/data/modmerge2.xml
new file mode 100644
index 0000000..e7f17c4
--- /dev/null
+++ b/tests/yanglint/data/modmerge2.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmerge">
+  <en>zero</en>
+</mmc>
diff --git a/tests/yanglint/data/modmerge3.xml b/tests/yanglint/data/modmerge3.xml
new file mode 100644
index 0000000..6ef857e
--- /dev/null
+++ b/tests/yanglint/data/modmerge3.xml
@@ -0,0 +1,3 @@
+<mmc xmlns="urn:yanglint:modmerge">
+  <lf>str</lf>
+</mmc>
diff --git a/tests/yanglint/data/modnotif.xml b/tests/yanglint/data/modnotif.xml
new file mode 100644
index 0000000..81cab21
--- /dev/null
+++ b/tests/yanglint/data/modnotif.xml
@@ -0,0 +1,5 @@
+<con xmlns="urn:yanglint:modnotif">
+  <nfn>
+    <lf>nested</lf>
+  </nfn>
+</con>
diff --git a/tests/yanglint/data/modnotif2.xml b/tests/yanglint/data/modnotif2.xml
new file mode 100644
index 0000000..fc75b57
--- /dev/null
+++ b/tests/yanglint/data/modnotif2.xml
@@ -0,0 +1,3 @@
+<nfg xmlns="urn:yanglint:modnotif">
+  <lf>top</lf>
+</nfg>
diff --git a/tests/yanglint/data/modnotif2_nc.xml b/tests/yanglint/data/modnotif2_nc.xml
new file mode 100644
index 0000000..c87cfa0
--- /dev/null
+++ b/tests/yanglint/data/modnotif2_nc.xml
@@ -0,0 +1,6 @@
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+  <eventTime>2010-12-06T08:00:01Z</eventTime>
+  <nfg xmlns="urn:yanglint:modnotif">
+    <lf>top</lf>
+  </nfg>
+</notification>
diff --git a/tests/yanglint/data/modnotif_ds.xml b/tests/yanglint/data/modnotif_ds.xml
new file mode 100644
index 0000000..efd835b
--- /dev/null
+++ b/tests/yanglint/data/modnotif_ds.xml
@@ -0,0 +1 @@
+<con xmlns="urn:yanglint:modnotif"></con>
diff --git a/tests/yanglint/data/modnotif_nc.xml b/tests/yanglint/data/modnotif_nc.xml
new file mode 100644
index 0000000..39a3440
--- /dev/null
+++ b/tests/yanglint/data/modnotif_nc.xml
@@ -0,0 +1,8 @@
+<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
+  <eventTime>2010-12-06T08:00:01Z</eventTime>
+  <con xmlns="urn:yanglint:modnotif">
+    <nfn>
+      <lf>nested</lf>
+    </nfn>
+  </con>
+</notification>
diff --git a/tests/yanglint/data/modoper_leafref_action.xml b/tests/yanglint/data/modoper_leafref_action.xml
new file mode 100644
index 0000000..7ccf29f
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_action.xml
@@ -0,0 +1,8 @@
+<cond xmlns="urn:yanglint:modoper-leafref">
+  <list>
+    <klf>key_val</klf>
+    <act>
+      <lfi>rw</lfi>
+    </act>
+  </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_action_reply.xml b/tests/yanglint/data/modoper_leafref_action_reply.xml
new file mode 100644
index 0000000..39ec672
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_action_reply.xml
@@ -0,0 +1,8 @@
+<cond xmlns="urn:yanglint:modoper-leafref">
+  <list>
+    <klf>key_val</klf>
+    <act>
+      <lfo>rw</lfo>
+    </act>
+  </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_ds.xml b/tests/yanglint/data/modoper_leafref_ds.xml
new file mode 100644
index 0000000..f934b9b
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_ds.xml
@@ -0,0 +1,9 @@
+<mcc xmlns="urn:yanglint:modconfig">
+  <lft>rw</lft>
+  <lff>ro</lff>
+</mcc>
+<cond xmlns="urn:yanglint:modoper-leafref">
+  <list>
+    <klf>key_val</klf>
+  </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_notif.xml b/tests/yanglint/data/modoper_leafref_notif.xml
new file mode 100644
index 0000000..2c56b67
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_notif.xml
@@ -0,0 +1,3 @@
+<notifg xmlns="urn:yanglint:modoper-leafref">
+  <lfr>rw</lfr>
+</notifg>
diff --git a/tests/yanglint/data/modoper_leafref_notif2.xml b/tests/yanglint/data/modoper_leafref_notif2.xml
new file mode 100644
index 0000000..466697c
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_notif2.xml
@@ -0,0 +1,8 @@
+<cond xmlns="urn:yanglint:modoper-leafref">
+  <list>
+    <klf>key_val</klf>
+    <notif>
+      <lfn>rw</lfn>
+    </notif>
+  </list>
+</cond>
diff --git a/tests/yanglint/data/modoper_leafref_notif_err.xml b/tests/yanglint/data/modoper_leafref_notif_err.xml
new file mode 100644
index 0000000..1622ded
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_notif_err.xml
@@ -0,0 +1,7 @@
+<mcc xmlns="urn:yanglint:modconfig">
+  <lft>rw</lft>
+  <lff>ro</lff>
+</mcc>
+<notifg xmlns="urn:yanglint:modoper-leafref">
+  <lf>rw</lf>
+</notifg>
diff --git a/tests/yanglint/data/modoper_leafref_rpc.xml b/tests/yanglint/data/modoper_leafref_rpc.xml
new file mode 100644
index 0000000..b294544
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_rpc.xml
@@ -0,0 +1,3 @@
+<rpcg xmlns="urn:yanglint:modoper-leafref">
+  <lfi>rw</lfi>
+</rpcg>
diff --git a/tests/yanglint/data/modoper_leafref_rpc_reply.xml b/tests/yanglint/data/modoper_leafref_rpc_reply.xml
new file mode 100644
index 0000000..e8f7af3
--- /dev/null
+++ b/tests/yanglint/data/modoper_leafref_rpc_reply.xml
@@ -0,0 +1,5 @@
+<rpcg xmlns="urn:yanglint:modoper-leafref">
+  <cono>
+    <lfo>rw</lfo>
+  </cono>
+</rpcg>
diff --git a/tests/yanglint/data/modrpc.xml b/tests/yanglint/data/modrpc.xml
new file mode 100644
index 0000000..a4f924d
--- /dev/null
+++ b/tests/yanglint/data/modrpc.xml
@@ -0,0 +1,3 @@
+<rpc xmlns="urn:yanglint:modrpc">
+  <lfi>some_input</lfi>
+</rpc>
diff --git a/tests/yanglint/data/modrpc_nc.xml b/tests/yanglint/data/modrpc_nc.xml
new file mode 100644
index 0000000..78d3149
--- /dev/null
+++ b/tests/yanglint/data/modrpc_nc.xml
@@ -0,0 +1,6 @@
+<rpc message-id="101"
+     xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <rpc xmlns="urn:yanglint:modrpc">
+    <lfi>some_input</lfi>
+  </rpc>
+</rpc>
diff --git a/tests/yanglint/data/modrpc_reply.xml b/tests/yanglint/data/modrpc_reply.xml
new file mode 100644
index 0000000..632971c
--- /dev/null
+++ b/tests/yanglint/data/modrpc_reply.xml
@@ -0,0 +1,5 @@
+<rpc xmlns="urn:yanglint:modrpc">
+  <con>
+    <lfo>-56</lfo>
+  </con>
+</rpc>
diff --git a/tests/yanglint/data/modrpc_reply_nc.xml b/tests/yanglint/data/modrpc_reply_nc.xml
new file mode 100644
index 0000000..da2a01c
--- /dev/null
+++ b/tests/yanglint/data/modrpc_reply_nc.xml
@@ -0,0 +1,6 @@
+<rpc-reply message-id="101"
+     xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <con xmlns="urn:yanglint:modrpc">
+    <lfo>-56</lfo>
+  </con>
+</rpc-reply>
diff --git a/tests/yanglint/data/modsm.xml b/tests/yanglint/data/modsm.xml
new file mode 100644
index 0000000..bb0793c
--- /dev/null
+++ b/tests/yanglint/data/modsm.xml
@@ -0,0 +1,3 @@
+<root xmlns="urn:yanglint:modsm">
+  <lfl xmlns="urn:yanglint:modleaf">7</lfl>
+</root>
diff --git a/tests/yanglint/data/modsm2.xml b/tests/yanglint/data/modsm2.xml
new file mode 100644
index 0000000..ff6f103
--- /dev/null
+++ b/tests/yanglint/data/modsm2.xml
@@ -0,0 +1,4 @@
+<root xmlns="urn:yanglint:modsm">
+  <lfl xmlns="urn:yanglint:modleaf">7</lfl>
+  <alf xmlns="urn:yanglint:modsm-augment">str</alf>
+</root>
diff --git a/tests/yanglint/data/modsm_ctx_ext.xml b/tests/yanglint/data/modsm_ctx_ext.xml
new file mode 100644
index 0000000..e80141a
--- /dev/null
+++ b/tests/yanglint/data/modsm_ctx_ext.xml
@@ -0,0 +1,20 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set>
+    <name>test-set</name>
+    <module>
+      <name>modleaf</name>
+      <namespace>urn:yanglint:modleaf</namespace>
+    </module>
+  </module-set>
+  <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set-id>1</module-set-id>
+</modules-state>
+<schema-mounts xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount">
+  <mount-point>
+    <module>modsm</module>
+    <label>root</label>
+    <inline></inline>
+  </mount-point>
+</schema-mounts>
diff --git a/tests/yanglint/data/modsm_ctx_main.xml b/tests/yanglint/data/modsm_ctx_main.xml
new file mode 100644
index 0000000..5405d4d
--- /dev/null
+++ b/tests/yanglint/data/modsm_ctx_main.xml
@@ -0,0 +1,17 @@
+<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set>
+    <name>main-set</name>
+    <module>
+      <name>modsm</name>
+      <namespace>urn:yanglint:modsm</namespace>
+    </module>
+    <module>
+      <name>modsm-augment</name>
+      <namespace>urn:yanglint:modsm-augment</namespace>
+    </module>
+  </module-set>
+  <content-id>1</content-id>
+</yang-library>
+<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
+  <module-set-id>1</module-set-id>
+</modules-state>
diff --git a/tests/yanglint/interactive/add.test b/tests/yanglint/interactive/add.test
new file mode 100644
index 0000000..d1cacc1
--- /dev/null
+++ b/tests/yanglint/interactive/add.test
@@ -0,0 +1,59 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test add_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "add $mdir/modleafref.yang"
+    ly_cmd "list" "I modleafref\r.*I modleaf"
+}}
+
+test add_disable_searchdir_once {add --disable-searchdir} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {!ctest} -body {
+    ly_cmd "add $mdir/modimp-cwd.yang"
+    ly_cmd "clear"
+    ly_cmd_err "add -D $mdir/modimp-cwd.yang" "not found in local searchdirs"
+}}
+
+test add_disable_searchdir_twice {add -D -D} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {!ctest} -body {
+    ly_cmd "add $mdir/ietf-ip.yang"
+    ly_cmd "clear"
+    ly_cmd_err "add -D -D $mdir/ietf-ip.yang" "Loading \"ietf-interfaces\" module failed."
+}}
+
+test add_with_feature {Add module with feature} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "add --feature modfeature:ftr2 $mdir/modfeature.yang"
+    ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(off\\)\r\n\tftr2 \\(on\\)"
+}}
+
+test add_make_implemented_once {add --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_ignore "add $mdir/modmust.yang"
+    ly_cmd "list" "I modmust\r.*i modleaf"
+    ly_cmd "clear"
+    ly_ignore "add -i $mdir/modmust.yang"
+    ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test add_make_implemented_twice {add -i -i} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "add $mdir/modimp-type.yang"
+    ly_cmd "list" "I modimp-type\r.*i modtypedef"
+    ly_cmd "clear"
+    ly_cmd "add -i -i $mdir/modimp-type.yang"
+    ly_cmd "list" "I modimp-type\r.*I modtypedef"
+}}
+
+test add_extended_leafref_enabled {Valid module with --extended-leafref option} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "add -X $mdir/modextleafref.yang"
+}}
+
+test add_extended_leafref_disabled {Expected error if --extended-leafref is not set} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd_err "add $mdir/modextleafref.yang" "Unexpected XPath token \"FunctionName\""
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/all.tcl b/tests/yanglint/interactive/all.tcl
new file mode 100644
index 0000000..b22a5ab
--- /dev/null
+++ b/tests/yanglint/interactive/all.tcl
@@ -0,0 +1,15 @@
+package require tcltest
+
+# Hook to determine if any of the tests failed.
+# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0.
+proc tcltest::cleanupTestsHook {} {
+    variable numTests
+    set ::exitCode [expr {$numTests(Failed) > 0}]
+}
+
+if {[info exists ::env(TESTS_DIR)]} {
+    tcltest::configure -testdir "$env(TESTS_DIR)/interactive"
+}
+
+tcltest::runAllTests
+exit $exitCode
diff --git a/tests/yanglint/interactive/clear.test b/tests/yanglint/interactive/clear.test
new file mode 100644
index 0000000..cac0810
--- /dev/null
+++ b/tests/yanglint/interactive/clear.test
@@ -0,0 +1,53 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test clear_searchpath {searchpath is also deleted} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "searchpath ./"
+    ly_cmd "clear"
+    ly_cmd "searchpath" "List of the searchpaths:" -ex
+}}
+
+test clear_make_implemented_once {clear --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -i"
+    ly_cmd "add $mdir/modmust.yang"
+    ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test clear_make_implemented_twice {clear -i -i} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -i -i"
+    ly_cmd "add $mdir/modmust.yang"
+    ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test clear_ietf_yang_library {clear --yang-library} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    # add models
+    ly_cmd "clear -y"
+    ly_cmd "list" "I ietf-yang-library"
+}}
+
+test clear_ylf_list {apply --yang-library-file and check result by --list} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -Y $ddir/modimp_type_ctx.xml"
+    ly_cmd "list" "I modimp-type.*i modtypedef"
+}}
+
+test clear_ylf_make_implemented {apply --yang-library-file and --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -Y $ddir/modimp_type_ctx.xml -i -i"
+    ly_cmd "list" "I modimp-type.*I modtypedef"
+}}
+
+test clear_ylf_augment_ctx {Setup context by yang-library-file and augment module} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -Y $ddir/modconfig_ctx.xml"
+    ly_cmd "add $mdir/modconfig-augment.yang"
+    ly_cmd "print -f tree modconfig" "mca:alf"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/completion.test b/tests/yanglint/interactive/completion.test
new file mode 100644
index 0000000..86ded1f
--- /dev/null
+++ b/tests/yanglint/interactive/completion.test
@@ -0,0 +1,69 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+
+variable ly_cleanup {
+    ly_ignore
+    ly_exit
+}
+
+test completion_hints_ietf_ip {Completion and hints for ietf-ip.yang} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "add $mdir/ietf-ip.yang"
+
+    # completion and hint
+    ly_completion "print -f info -P " "print -f info -P /ietf-"
+
+    set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"}
+    ly_hint "" "print -f info -P /ietf-" $hints
+
+    # double completion
+    ly_completion "i" "print -f info -P /ietf-interfaces:interfaces"
+    ly_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface"
+
+    # a lot of hints
+    set hints {"/ietf-interfaces:interfaces/interface"
+        "/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description"
+        "/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled"
+        "/ietf-interfaces:interfaces/interface/link-up-down-trap-enable"
+        "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"
+    }
+    ly_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints
+
+    # double tab
+    ly_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv"
+    ly_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4"
+    set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled"
+        "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu"
+        "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor"
+    }
+    ly_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints
+
+    # no more completion
+    ly_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
+}}
+
+# Note that somehow a command is automatically sent again (\t\t replaced by \r) after the hints.
+# But that doesn't affect the test because the tests only focus on the word in the hint.
+
+test hint_data_file {Show file hints for command data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data $mdir\t\t" "data $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_format {Show print hints for command data --format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data -f \t\t" "data -f " "xml.*"
+}}
+
+test hint_data_file_after_opt {Show file hints after option with argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data -f xml $mdir\t\t" "data -f xml $mdir" "modleaf.yang.*"
+}}
+
+test hint_data_file_after_opt2 {Show file hints after option without argument} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_hint "data -m $mdir\t\t" "data -m $mdir" "modleaf.yang.*"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_default.test b/tests/yanglint/interactive/data_default.test
new file mode 100644
index 0000000..1953acc
--- /dev/null
+++ b/tests/yanglint/interactive/data_default.test
@@ -0,0 +1,41 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mods "ietf-netconf-with-defaults moddefault"
+set data "$::env(TESTS_DIR)/data/moddefault.xml"
+
+test data_default_not_set {Print data without --default parameter} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load $mods"
+    ly_cmd "data -f xml $data" "</lf>.*</di>\r\n</mdc>"
+    ly_cmd "data -f json $data" "lf\".*di\"\[^\"]*"
+}}
+
+test data_default_all {data --default all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load $mods"
+    ly_cmd "data -d all -f xml $data" "</lf>.*</di>.*</ds>\r\n</mdc>"
+    ly_cmd "data -d all -f json $data" "lf\".*di\".*ds\"\[^\"]*"
+}}
+
+test data_default_all_tagged {data --default all-tagged} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load $mods"
+    ly_cmd "data -d all-tagged -f xml $data" "</lf>.*<di.*default.*</di>.*<ds.*default.*</ds>\r\n</mdc>"
+    ly_cmd "data -d all-tagged -f json $data" "lf\".*di\".*ds\".*@ds\".*default\"\[^\"]*"
+}}
+
+test data_default_trim {data --default trim} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load $mods"
+    ly_cmd "data -d trim -f xml $data" "</lf>\r\n</mdc>"
+    ly_cmd "data -d trim -f json $data" "lf\"\[^\"]*"
+}}
+
+test data_default_implicit_tagged {data --default implicit-tagged} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load $mods"
+    ly_cmd "data -d implicit-tagged -f xml $data" "</lf>.*<di>5</di>.*<ds.*default.*</ds>\r\n</mdc>"
+    ly_cmd "data -d implicit-tagged -f json $data" "lf\".*di\"\[^@]*ds\".*default\"\[^\"]*"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_format.test b/tests/yanglint/interactive/data_format.test
new file mode 100644
index 0000000..dc4b7e0
--- /dev/null
+++ b/tests/yanglint/interactive/data_format.test
@@ -0,0 +1,23 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_format_xml {Print data in xml format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "data -f xml $ddir/modleaf.xml" "<lfl xmlns=\"urn:yanglint:modleaf\">7</lfl>"
+}}
+
+test data_format_json {Print data in json format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "data -f json $ddir/modleaf.xml" "{\r\n  \"modleaf:lfl\": 7\r\n}"
+}}
+
+test data_format_lyb_err {Print data in lyb format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd_err "data -f lyb $ddir/modleaf.xml" "The LYB format requires the -o"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_in_format.test b/tests/yanglint/interactive/data_in_format.test
new file mode 100644
index 0000000..cc5f37e
--- /dev/null
+++ b/tests/yanglint/interactive/data_in_format.test
@@ -0,0 +1,21 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_in_format_xml {--in-format xml} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "data -F xml $ddir/modleaf.dxml"
+    ly_cmd_err "data -F json $ddir/modleaf.dxml" "Failed to parse"
+    ly_cmd_err "data -F lyb $ddir/modleaf.dxml" "Failed to parse"
+}}
+
+test data_in_format_json {--in-format json} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "data -F json $ddir/modleaf.djson"
+    ly_cmd_err "data -F xml $ddir/modleaf.djson" "Failed to parse"
+    ly_cmd_err "data -F lyb $ddir/modleaf.djson" "Failed to parse"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_merge.test b/tests/yanglint/interactive/data_merge.test
new file mode 100644
index 0000000..38754c7
--- /dev/null
+++ b/tests/yanglint/interactive/data_merge.test
@@ -0,0 +1,33 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_merge_basic {Data is merged and the node is added} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modmerge"
+    ly_cmd "data -m -f xml $ddir/modmerge.xml $ddir/modmerge3.xml" "<en>.*<lm>.*<lf>"
+}}
+
+test data_merge_validation_failed {Data is merged but validation failed.} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modmerge"
+    ly_cmd "data $ddir/modmerge.xml"
+    ly_cmd "data $ddir/modmerge2.xml"
+    ly_cmd "data -m $ddir/modmerge2.xml $ddir/modmerge.xml"
+    ly_cmd_err "data -m $ddir/modmerge.xml $ddir/modmerge2.xml" "Merged data are not valid"
+}}
+
+test data_merge_dataconfig {The merge option has effect only for 'data' and 'config' TYPEs} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc modnotif modconfig modleaf"
+    set wrn1 "option has effect only for"
+    ly_cmd_wrn "data -m -t rpc $ddir/modrpc.xml $ddir/modrpc.xml" $wrn1
+    ly_cmd_wrn "data -m -t notif $ddir/modnotif2.xml $ddir/modnotif2.xml" $wrn1
+    ly_cmd_wrn "data -m -t get $ddir/modleaf.xml $ddir/modconfig.xml" $wrn1
+    ly_cmd_wrn "data -m -t getconfig $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+    ly_cmd_wrn "data -m -t edit $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+    ly_cmd "data -m -t config $ddir/modleaf.xml $ddir/modconfig2.xml"
+    ly_cmd "data -m -t data $ddir/modleaf.xml $ddir/modconfig.xml"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_not_strict.test b/tests/yanglint/interactive/data_not_strict.test
new file mode 100644
index 0000000..201a5a9
--- /dev/null
+++ b/tests/yanglint/interactive/data_not_strict.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir $::env(TESTS_DIR)/data
+
+test data_no_strict_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd_err "data $ddir/modmandatory.xml" "No module with namespace \"urn:yanglint:modmandatory\" in the context."
+    ly_cmd "data -n $ddir/modmandatory.xml"
+}}
+
+test data_no_strict_invalid_data {validation with --no-strict but data are invalid} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    set errmsg "Mandatory node \"lft\" instance does not exist."
+    ly_cmd "load modmandatory"
+    ly_cmd_err "data -n $ddir/modmandatory_invalid.xml" $errmsg
+}}
+
+test data_no_strict_ignore_invalid_data {--no-strict ignore invalid data if no schema is provided} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "data -f xml -n $ddir/modmandatory_invalid.xml $ddir/modleaf.xml" "modleaf.*</lfl>$"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_operational.test b/tests/yanglint/interactive/data_operational.test
new file mode 100644
index 0000000..c0c7b1c
--- /dev/null
+++ b/tests/yanglint/interactive/data_operational.test
@@ -0,0 +1,86 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types"
+
+test data_operational_twice {it is not allowed to specify more than one --operational parameter} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times"
+}}
+
+test data_operational_no_type {--operational should be with parameter --type} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd_wrn "data -O $ddir/modconfig.xml $ddir/modoper_leafref_notif.xml" $err1
+}}
+
+test data_operational_missing {--operational is omitted and the datastore contents is in the data file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd_err "data $ddir/modoper_leafref_notif_err.xml" "Failed to parse input data file"
+}}
+
+test data_operational_wrong_type {data are not defined as an operation} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd_wrn "data -t data -O $ddir/modconfig.xml $ddir/modleaf.xml" $err1
+}}
+
+test data_operational_datastore_with_unknown_data {unknown data are ignored} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc"
+    ly_cmd "data -t rpc -O $ddir/modmandatory_invalid.xml $ddir/modrpc.xml"
+}}
+
+test data_operational_empty_datastore {datastore is considered empty because it contains unknown data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc modnotif"
+    ly_cmd "data -t rpc -O $ddir/modmandatory_invalid.xml $ddir/modrpc.xml"
+    set msg "parent \"/modnotif:con\" not found in the operational data"
+    ly_cmd_err "data -t notif -O $ddir/modmandatory_invalid.xml $ddir/modnotif.xml" $msg
+}}
+
+test data_operational_notif_leafref {--operational data is referenced from notification-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t notif -O $ddir/modconfig.xml $ddir/modoper_leafref_notif.xml"
+}}
+
+test data_operational_nested_notif_leafref {--operational data is referenced from nested-notification-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t notif -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_notif2.xml"
+}}
+
+test data_operational_nested_notif_parent_missing {--operational data are invalid due to missing parent node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    set msg "klf='key_val']\" not found in the operational data"
+    ly_cmd_err "data -t notif -O $ddir/modconfig.xml $ddir/modoper_leafref_notif2.xml" $msg
+}}
+
+test data_operational_action_leafref {--operational data is referenced from action-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t rpc -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_action.xml"
+}}
+
+test data_operational_action_reply_leafref {--operational data is referenced from action-leafref output} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t reply -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_action_reply.xml"
+}}
+
+test data_operational_rpc_leafref {--operational data is referenced from rpc-leafref} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t rpc -O $ddir/modconfig.xml $ddir/modoper_leafref_rpc.xml"
+}}
+
+test data_operational_rpc_reply_leafref {--operational data is referenced from rpc-leafref output} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modoper-leafref"
+    ly_cmd "data -t reply -O $ddir/modconfig.xml $ddir/modoper_leafref_rpc_reply.xml"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_present.test b/tests/yanglint/interactive/data_present.test
new file mode 100644
index 0000000..4bba596
--- /dev/null
+++ b/tests/yanglint/interactive/data_present.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_present_via_mandatory {validation of mandatory-stmt will pass only with the --present} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf modmandatory"
+    ly_cmd_err "data $ddir/modleaf.xml" "Mandatory node \"lft\" instance does not exist."
+    ly_cmd "data -e $ddir/modleaf.xml"
+}}
+
+test data_present_merge {validation with --present and --merge} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf modmandatory moddefault"
+    ly_cmd_err "data -m $ddir/modleaf.xml $ddir/moddefault.xml" "Mandatory node \"lft\" instance does not exist."
+    ly_cmd "data -e -m $ddir/modleaf.xml $ddir/moddefault.xml"
+}}
+
+test data_present_merge_invalid {using --present and --merge but data are invalid} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf modmandatory"
+    ly_cmd_err "data -e -m $ddir/modleaf.xml $ddir/modmandatory_invalid.xml" "Mandatory node \"lft\" instance does not exist."
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_type.test b/tests/yanglint/interactive/data_type.test
new file mode 100644
index 0000000..a442813
--- /dev/null
+++ b/tests/yanglint/interactive/data_type.test
@@ -0,0 +1,140 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ddir "$::env(TESTS_DIR)/data"
+
+test data_type_data {data --type data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modconfig"
+    ly_cmd "data -t data $ddir/modconfig.xml"
+}}
+
+test data_type_config {data --type config} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modconfig"
+    ly_cmd_err "data -t config $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+    ly_cmd "data -t config $ddir/modconfig2.xml"
+}}
+
+test data_type_get {data --type get} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleafref"
+    ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value"
+    ly_cmd "data -t get $ddir/modleafref2.xml"
+}}
+
+test data_type_getconfig_no_state {No state node for data --type getconfig} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modconfig"
+    ly_cmd_err "data -t getconfig $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+    ly_cmd "data -t getconfig $ddir/modconfig2.xml"
+}}
+
+test data_type_getconfig_parse_only {No validation performed for data --type getconfig} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleafref"
+    ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value"
+    ly_cmd "data -t getconfig $ddir/modleafref2.xml"
+}}
+
+test data_type_edit_no_state {No state node for data --type edit} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modconfig"
+    ly_cmd_err "data -t edit $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+    ly_cmd "data -t edit $ddir/modconfig2.xml"
+}}
+
+test data_type_edit_parse_only {No validation performed for data --type edit} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleafref"
+    ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value"
+    ly_cmd "data -t edit $ddir/modleafref2.xml"
+}}
+
+test data_type_rpc {Validation of rpc-statement by data --type rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc modleaf"
+    ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "data -t rpc $ddir/modrpc.xml"
+}}
+
+test data_type_rpc_nc {Validation of rpc-statement by data --type nc-rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc modleaf ietf-netconf"
+    ly_cmd_err "data -t nc-rpc $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+    ly_cmd "data -t nc-rpc $ddir/modrpc_nc.xml"
+}}
+
+test data_type_rpc_reply {Validation of rpc-reply by data --type reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc modleaf"
+    ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd_wrn "data -t reply -R $ddir/modrpc.xml $ddir/modrpc_reply.xml" "needed only for NETCONF"
+    ly_cmd "data -t reply $ddir/modrpc_reply.xml"
+}}
+
+test data_type_rpc_reply_nc {Validation of rpc-reply by data --type nc-reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modrpc modleaf"
+    ly_cmd_err "data -t nc-reply -R $ddir/modrpc_nc.xml $ddir/modleaf.xml" "Missing NETCONF <rpc-reply> envelope"
+    ly_cmd_err "data -t nc-reply $ddir/modrpc_reply_nc.xml" "Missing source RPC"
+    ly_cmd "data -t nc-reply -R $ddir/modrpc_nc.xml $ddir/modrpc_reply_nc.xml"
+}}
+
+test data_type_rpc_action {Validation of action-statement by data --type rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modaction modleaf"
+    ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "data -t rpc -O $ddir/modaction_ds.xml $ddir/modaction.xml"
+}}
+
+test data_type_rpc_action_nc {Validation of action-statement by data --type nc-rpc} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modaction modleaf"
+    ly_cmd_err "data -t nc-rpc $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+    ly_cmd "data -t nc-rpc -O $ddir/modaction_ds.xml $ddir/modaction_nc.xml"
+}}
+
+test data_type_rpc_action_reply {Validation of action-reply by data --type reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modaction modleaf"
+    ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "data -t reply -O $ddir/modaction_ds.xml $ddir/modaction_reply.xml"
+}}
+
+test data_type_rpc_action_reply_nc {Validation of action-reply by data --type nc-reply} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modaction modleaf"
+    ly_cmd_err "data -t nc-reply -R $ddir/modaction_nc.xml $ddir/modleaf.xml" "Missing NETCONF <rpc-reply> envelope"
+    ly_cmd_err "data -t nc-reply $ddir/modaction_reply_nc.xml" "Missing source RPC"
+    ly_cmd_err "data -t nc-reply -R $ddir/modaction_nc.xml $ddir/modaction_reply_nc.xml" "operational parameter needed"
+    ly_cmd "data -t nc-reply -O $ddir/modaction_ds.xml -R $ddir/modaction_nc.xml $ddir/modaction_reply_nc.xml"
+}}
+
+test data_type_notif {Validation of notification-statement by data --type notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modnotif modleaf"
+    ly_cmd_err "data -t notif $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "data -t notif $ddir/modnotif2.xml"
+}}
+
+test data_type_notif_nc {Validation of notification-statement by data --type nc-notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modnotif modleaf ietf-netconf"
+    ly_cmd_err "data -t nc-notif $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+    ly_cmd "data -t nc-notif $ddir/modnotif2_nc.xml"
+}}
+
+test data_type_notif_nested {Validation of nested-notification-statement by data --type notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modnotif modleaf"
+    ly_cmd "data -t notif -O $ddir/modnotif_ds.xml $ddir/modnotif.xml"
+}}
+
+test data_type_notif_nested_nc {Validation of nested-notification-statement by data --type nc-notif} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modnotif modleaf ietf-netconf"
+    ly_cmd_err "data -t nc-notif $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+    ly_cmd "data -t nc-notif -O $ddir/modnotif_ds.xml $ddir/modnotif_nc.xml"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/data_xpath.test b/tests/yanglint/interactive/data_xpath.test
new file mode 100644
index 0000000..398cb9f
--- /dev/null
+++ b/tests/yanglint/interactive/data_xpath.test
@@ -0,0 +1,57 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set data "$::env(TESTS_DIR)/data/moddatanodes.xml"
+
+test data_xpath_empty {--xpath to missing node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    ly_cmd "data -x /moddatanodes:dnc/mis $data" "Empty"
+}}
+
+test data_xpath_leaf {--xpath to leaf node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    ly_cmd "data -x /moddatanodes:dnc/lf $data" "leaf \"lf\" \\(value: \"x\"\\)"
+}}
+
+test data_xpath_leaflist {--xpath to leaf-list node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    set r1 "leaf-list \"lfl\" \\(value: \"1\"\\)"
+    set r2 "leaf-list \"lfl\" \\(value: \"2\"\\)"
+    ly_cmd "data -x /moddatanodes:dnc/lfl $data" "$r1\r\n  $r2"
+}}
+
+test data_xpath_list {--xpath to list} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    set r1 "list \"lt\" \\(\"kalf\": \"ka1\"; \"kblf\": \"kb1\";\\)"
+    set r2 "list \"lt\" \\(\"kalf\": \"ka2\"; \"kblf\": \"kb2\";\\)"
+    ly_cmd "data -x /moddatanodes:dnc/con/lt $data" "$r1\r\n  $r2"
+}}
+
+test data_xpath_container {--xpath to container} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    ly_cmd "data -x /moddatanodes:dnc/con $data" "container \"con\""
+}}
+
+test data_xpath_wrong_path {--xpath to a non-existent node} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    ly_cmd_err "data -x /moddatanodes:dnc/wrng $data" "xpath failed"
+}}
+
+test data_xpath_err_format {--xpath cannot be combined with --format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes"
+    ly_cmd_err "data -f xml -x /moddatanodes:dnc/lf $data" "option cannot be combined"
+}}
+
+test data_xpath_err_default {--xpath cannot be combined with --default} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load moddatanodes ietf-netconf-with-defaults"
+    ly_cmd_err "data -d all -x /moddatanodes:dnc/lf $data" "option cannot be combined"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/debug.test b/tests/yanglint/interactive/debug.test
new file mode 100644
index 0000000..8a64c92
--- /dev/null
+++ b/tests/yanglint/interactive/debug.test
@@ -0,0 +1,33 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test debug_dict {Check debug message DICT} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+    ly_cmd "verb debug"
+    ly_cmd "debug dict"
+    ly_cmd "load modleaf" "DICT"
+}}
+
+test debug_xpath {Check debug message XPATH} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+    ly_cmd "verb debug"
+    ly_cmd "debug xpath"
+    ly_cmd "load modmust" "XPATH"
+}}
+
+test debug_dep_sets {Check debug message DEPSETS} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+    ly_cmd "verb debug"
+    ly_cmd "debug dep-sets"
+    ly_cmd "load modleaf" "DEPSETS"
+}}
+
+test debug_depsets_xpath {Check debug message DEPSETS and XPATH} {
+-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body {
+    ly_cmd "verb debug"
+    ly_cmd "debug dep-sets xpath"
+    ly_cmd "load modmust" "DEPSETS.*XPATH"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/extdata.test b/tests/yanglint/interactive/extdata.test
new file mode 100644
index 0000000..e253d1a
--- /dev/null
+++ b/tests/yanglint/interactive/extdata.test
@@ -0,0 +1,63 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+
+test extdata_set_clear {Set and clear extdata file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "extdata" "No file set"
+    ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+    ly_cmd "extdata" "$ddir/modsm_ctx_ext.xml"
+    ly_cmd "extdata -c"
+    ly_cmd "extdata" "No file set"
+}}
+
+test extdata_clear_cmd {Clear extdata file by 'clear' command} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+    ly_cmd "clear"
+    ly_cmd "extdata" "No file set"
+}}
+
+test extdata_one_only {Only one file for extdata} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd_err "extdata $ddir/modsm_ctx_ext.xml $ddir/modsm_ctx_ext.xml" "Only one file must be entered"
+}}
+
+test extdata_schema_mount_tree {Print tree output of a model with Schema Mount} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -y"
+    ly_cmd "searchpath $mdir"
+    ly_cmd "load modsm"
+    ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+    ly_cmd "print -f tree modsm" "--mp root.*--rw lfl/"
+}}
+
+test ext_data_schema_mount_tree_yanglibfile {Print tree output of a model with Schema Mount and --yang-library-file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -Y $ddir/modsm_ctx_main.xml"
+    ly_cmd "searchpath $mdir"
+    ly_cmd "load modsm"
+    ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+    ly_cmd "print -f tree modsm" "--mp root.*--rw lfl/.*--rw msa:alf?"
+}}
+
+test ext_data_schema_mount_xml {Validating and printing mounted data} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -y"
+    ly_cmd "searchpath $mdir"
+    ly_cmd "load modsm"
+    ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+    ly_cmd "data -f xml -t config $ddir/modsm.xml" "</lfl>"
+}}
+
+test ext_data_schema_mount_xml_yanglibfile {Validating and printing mounted data with --yang-library-file} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -Y $ddir/modsm_ctx_main.xml"
+    ly_cmd "searchpath $mdir"
+    ly_cmd "load modsm"
+    ly_cmd "extdata $ddir/modsm_ctx_ext.xml"
+    ly_cmd "data -f xml -t config $ddir/modsm2.xml" "</lfl>.*</alf>"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/feature.test b/tests/yanglint/interactive/feature.test
new file mode 100644
index 0000000..84bfa8e
--- /dev/null
+++ b/tests/yanglint/interactive/feature.test
@@ -0,0 +1,37 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+test feature_all_default {Default output of feature --all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "feature -a" "yang:\r\n\t(none)\r\n\r\nietf-yang-schema-mount:\r\n\t(none)\r\n" -ex
+}}
+
+test feature_all_add_module {Add module with only one feature and call feature --all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load --feature modfeature:ftr1 modfeature"
+    ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(on\\)\r\n\tftr2 \\(off\\)"
+}}
+
+test feature_all_on {Add module with all enabled features and call feature --all} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load --feature modfeature:* modfeature"
+    ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(on\\)\r\n\tftr2 \\(on\\)"
+}}
+
+test feature_one_module {Show features for one module} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load ietf-ip"
+    ly_cmd "feature -f ietf-ip" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" -ex
+}}
+
+test feature_more_modules {Show a mix of modules with and without features} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+
+    set features " -F modfeature:ftr1,ftr2\
+-F modleaf:\
+-F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
+
+    ly_cmd "load ietf-ip modleaf modfeature"
+    ly_cmd "feature -f modfeature modleaf ietf-ip" $features -ex
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/list.test b/tests/yanglint/interactive/list.test
new file mode 100644
index 0000000..ab59a32
--- /dev/null
+++ b/tests/yanglint/interactive/list.test
@@ -0,0 +1,34 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+namespace import uti::regex_xml_elements uti::regex_json_pairs
+
+set modules {ietf-yang-library ietf-inet-types}
+
+test list_basic {basic test} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "list" "ietf-yang-types"
+}}
+
+test list_format_xml {list --format xml} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -y"
+    ly_cmd "list -f xml" [regex_xml_elements $modules "name"]
+}}
+
+test list_format_json {list --format json} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -y"
+    ly_cmd "list -f json" [regex_json_pairs $modules "name"]
+}}
+
+test list_ietf_yang_library {Error due to missing ietf-yang-library} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd_err "list -f xml" "Module \"ietf-yang-library\" is not implemented."
+}}
+
+test list_bad_format {Error due to bad format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "clear -y"
+    ly_cmd_err "list -f csv" "Unknown output format csv"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/load.test b/tests/yanglint/interactive/load.test
new file mode 100644
index 0000000..a95d044
--- /dev/null
+++ b/tests/yanglint/interactive/load.test
@@ -0,0 +1,45 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+test load_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleafref"
+    ly_cmd "list" "I modleafref\r.*I modleaf"
+}}
+
+test load_with_feature {Load module with feature} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load --feature modfeature:ftr2 modfeature"
+    ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(off\\)\r\n\tftr2 \\(on\\)"
+}}
+
+test load_make_implemented_once {load --make-implemented} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_ignore "load modmust"
+    ly_cmd "list" "I modmust\r.*i modleaf"
+    ly_cmd "clear"
+    ly_cmd "searchpath $::env(YANG_MODULES_DIR)"
+    ly_cmd "load -i modmust"
+    ly_cmd "list" "I modmust\r.*I modleaf"
+}}
+
+test load_make_implemented_twice {load -i -i} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modimp-type"
+    ly_cmd "list" "I modimp-type\r.*i modtypedef"
+    ly_cmd "clear"
+    ly_cmd "searchpath $::env(YANG_MODULES_DIR)"
+    ly_cmd "load -i -i modimp-type"
+    ly_cmd "list" "I modimp-type\r.*I modtypedef"
+}}
+
+test load_extended_leafref_enabled {Valid module with --extended-leafref option} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load -X modextleafref"
+}}
+
+test load_extended_leafref_disabled {Expected error if --extended-leafref is not set} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd_err "load modextleafref" "Unexpected XPath token \"FunctionName\""
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/ly.tcl b/tests/yanglint/interactive/ly.tcl
new file mode 100644
index 0000000..4c56be4
--- /dev/null
+++ b/tests/yanglint/interactive/ly.tcl
@@ -0,0 +1,81 @@
+# @brief The main source of functions and variables for testing yanglint in the interactive mode.
+
+# For testing yanglint.
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/common.tcl" : "../common.tcl"}]
+# For testing any interactive tool.
+source "$::env(TESTS_DIR)/../tool_i.tcl"
+
+# The script continues by defining variables and functions specific to the interactive yanglint tool.
+
+# set the timeout to 5 seconds
+set timeout 5
+# prompt of yanglint
+set prompt "> "
+# turn off dialog between expect and yanglint
+log_user 0
+# setting some large terminal width
+stty columns 720
+
+# default setup for every unit test
+variable ly_setup {
+    spawn $TUT
+    ly_skip_warnings
+    # Searchpath is set, so modules can be loaded via the 'load' command.
+    ly_cmd "searchpath $::env(YANG_MODULES_DIR)"
+}
+
+# default cleanup for every unit test
+variable ly_cleanup {
+    ly_exit
+}
+
+# Skip no dir and/or no history warnings and prompt.
+proc ly_skip_warnings {} {
+    global prompt
+    expect -re "(YANGLINT.*)*$prompt" {}
+}
+
+# Send command 'cmd' to the process, expect error header and then check output string by 'pattern'.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex. It must not contain a prompt.
+proc ly_cmd_err {cmd pattern} {
+    global prompt
+
+    send -- "${cmd}\r"
+    expect -- "${cmd}\r\n"
+
+    expect {
+        -re "YANGLINT\\\[E\\\]: .*${pattern}.*\r\n${prompt}$" {}
+        -re "libyang\\\[\[0-9]+\\\]: .*${pattern}.*\r\n${prompt}$" {}
+        -re "\r\n${prompt}$" {
+            error "unexpected output:\n$expect_out(buffer)"
+        }
+    }
+}
+
+# Send command 'cmd' to the process, expect warning header and then check output string by 'pattern'.
+# Parameter cmd is a string of arguments.
+# Parameter pattern is a regex. It must not contain a prompt.
+proc ly_cmd_wrn {cmd pattern} {
+    ly_cmd_header $cmd "YANGLINT\\\[W\\\]:" $pattern
+}
+
+# Send 'exit' and wait for eof.
+proc ly_exit {} {
+    send "exit\r"
+    expect eof
+}
+
+# Check if yanglint is configured as DEBUG.
+# Return 1 on success.
+proc yanglint_debug {} {
+    global TUT
+    # Call non-interactive yanglint with --help.
+    set output [exec -- $TUT "-h"]
+    # Find option --debug.
+    if { [regexp -- "--debug=GROUPS" $output] } {
+        return 1
+    } else {
+        return 0
+    }
+}
diff --git a/tests/yanglint/interactive/modcwd.yang b/tests/yanglint/interactive/modcwd.yang
new file mode 100644
index 0000000..db33e73
--- /dev/null
+++ b/tests/yanglint/interactive/modcwd.yang
@@ -0,0 +1,4 @@
+module modcwd {
+  namespace "urn:yanglint:modcwd";
+  prefix mc;
+}
diff --git a/tests/yanglint/interactive/print.test b/tests/yanglint/interactive/print.test
new file mode 100644
index 0000000..8b9d740
--- /dev/null
+++ b/tests/yanglint/interactive/print.test
@@ -0,0 +1,77 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set ipv6_path "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address"
+
+test print_yang {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "print -f yang modleaf" "leaf lfl"
+}}
+
+test print_yang_submodule {Print submodule in yang format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modinclude"
+    ly_cmd "print -f yang modsub" "submodule modsub"
+}}
+
+test print_yin {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "print -f yin modleaf" "<leaf name=\"lfl\">"
+}}
+
+test print_yin_submodule {Print submodule in yin format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modinclude"
+    ly_cmd "print -f yin modsub" "<submodule name=\"modsub\""
+}}
+
+test print_info {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "print -f info modleaf" "status current"
+}}
+
+test print_info_path {Print subtree in info format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load ietf-ip"
+    ly_cmd "print -f info -P $ipv6_path" "^list address .* leaf prefix-length"
+}}
+
+test print_info_path_single_node {Print node in info format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load ietf-ip"
+    ly_cmd "print -f info -q -P $ipv6_path" "^list address .* IPv6 addresses on the interface.\";\r\n\}$"
+}}
+
+test print_tree {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modleaf"
+    ly_cmd "print -f tree modleaf" "\\+--rw lfl"
+}}
+
+test print_tree_submodule {Print submodule in tree format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load modinclude"
+    ly_cmd "print -f tree modsub" "submodule: modsub"
+}}
+
+test print_tree_path {Print subtree in tree format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load ietf-ip"
+    ly_cmd "print -f tree -P $ipv6_path" "\\+--rw address.*\\+--rw prefix-length"
+}}
+
+test print_tree_path_single_node {Print node in tree format} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load ietf-ip"
+    ly_cmd "print -f tree -q -P $ipv6_path" "\\+--rw address\\* \\\[ip\\\]$"
+}}
+
+test print_tree_path_single_node_line_length {Print node in the tree format and limit row size} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "load ietf-ip"
+    ly_cmd "print -f tree -L 20 -q -P $ipv6_path" "\\+--rw address\\*\r\n *\\\[ip\\\]$"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/interactive/searchpath.test b/tests/yanglint/interactive/searchpath.test
new file mode 100644
index 0000000..3bd6796
--- /dev/null
+++ b/tests/yanglint/interactive/searchpath.test
@@ -0,0 +1,24 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+variable ly_setup {
+    spawn $TUT
+    ly_skip_warnings
+}
+
+test searchpath_basic {} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "searchpath $mdir"
+    ly_cmd "searchpath" "$mdir"
+    ly_cmd "load modleaf"
+}}
+
+test searchpath_clear {searchpath --clear} {
+-setup $ly_setup -cleanup $ly_cleanup -body {
+    ly_cmd "searchpath $mdir"
+    ly_cmd "searchpath --clear"
+    ly_cmd_err "load modleaf" "Data model \"modleaf\" not found in local searchdirs"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/modules/ietf-interfaces.yang b/tests/yanglint/modules/ietf-interfaces.yang
new file mode 100644
index 0000000..ad64425
--- /dev/null
+++ b/tests/yanglint/modules/ietf-interfaces.yang
@@ -0,0 +1,725 @@
+module ietf-interfaces {
+
+  namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces";
+  prefix if;
+
+  import ietf-yang-types {
+    prefix yang;
+  }
+
+  organization
+    "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+  contact
+    "WG Web:   <http://tools.ietf.org/wg/netmod/>
+     WG List:  <mailto:netmod@ietf.org>
+
+     WG Chair: Thomas Nadeau
+               <mailto:tnadeau@lucidvision.com>
+
+     WG Chair: Juergen Schoenwaelder
+               <mailto:j.schoenwaelder@jacobs-university.de>
+
+     Editor:   Martin Bjorklund
+               <mailto:mbj@tail-f.com>";
+
+  description
+    "This module contains a collection of YANG definitions for
+     managing network interfaces.
+
+     Copyright (c) 2014 IETF Trust and the persons identified as
+     authors of the code.  All rights reserved.
+
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD License
+     set forth in Section 4.c of the IETF Trust's Legal Provisions
+     Relating to IETF Documents
+     (http://trustee.ietf.org/license-info).
+
+     This version of this YANG module is part of RFC 7223; see
+     the RFC itself for full legal notices.";
+
+  revision 2014-05-08 {
+    description
+      "Initial revision.";
+    reference
+      "RFC 7223: A YANG Data Model for Interface Management";
+  }
+
+  /*
+   * Typedefs
+   */
+
+  typedef interface-ref {
+    type leafref {
+      path "/if:interfaces/if:interface/if:name";
+    }
+    description
+      "This type is used by data models that need to reference
+       configured interfaces.";
+  }
+
+  typedef interface-state-ref {
+    type leafref {
+      path "/if:interfaces-state/if:interface/if:name";
+    }
+    description
+      "This type is used by data models that need to reference
+       the operationally present interfaces.";
+  }
+
+  /*
+   * Identities
+   */
+
+  identity interface-type {
+    description
+      "Base identity from which specific interface types are
+       derived.";
+  }
+
+  /*
+   * Features
+   */
+
+  feature arbitrary-names {
+    description
+      "This feature indicates that the device allows user-controlled
+       interfaces to be named arbitrarily.";
+  }
+  feature pre-provisioning {
+    description
+      "This feature indicates that the device supports
+       pre-provisioning of interface configuration, i.e., it is
+       possible to configure an interface whose physical interface
+       hardware is not present on the device.";
+  }
+
+  feature if-mib {
+    description
+      "This feature indicates that the device implements
+       the IF-MIB.";
+    reference
+      "RFC 2863: The Interfaces Group MIB";
+  }
+
+  /*
+   * Configuration data nodes
+   */
+
+  container interfaces {
+    description
+      "Interface configuration parameters.";
+
+    list interface {
+      key "name";
+
+      description
+        "The list of configured interfaces on the device.
+
+         The operational state of an interface is available in the
+         /interfaces-state/interface list.  If the configuration of a
+         system-controlled interface cannot be used by the system
+         (e.g., the interface hardware present does not match the
+         interface type), then the configuration is not applied to
+         the system-controlled interface shown in the
+         /interfaces-state/interface list.  If the configuration
+         of a user-controlled interface cannot be used by the system,
+         the configured interface is not instantiated in the
+         /interfaces-state/interface list.";
+
+     leaf name {
+        type string;
+        description
+          "The name of the interface.
+
+           A device MAY restrict the allowed values for this leaf,
+           possibly depending on the type of the interface.
+           For system-controlled interfaces, this leaf is the
+           device-specific name of the interface.  The 'config false'
+           list /interfaces-state/interface contains the currently
+           existing interfaces on the device.
+
+           If a client tries to create configuration for a
+           system-controlled interface that is not present in the
+           /interfaces-state/interface list, the server MAY reject
+           the request if the implementation does not support
+           pre-provisioning of interfaces or if the name refers to
+           an interface that can never exist in the system.  A
+           NETCONF server MUST reply with an rpc-error with the
+           error-tag 'invalid-value' in this case.
+
+           If the device supports pre-provisioning of interface
+           configuration, the 'pre-provisioning' feature is
+           advertised.
+
+           If the device allows arbitrarily named user-controlled
+           interfaces, the 'arbitrary-names' feature is advertised.
+
+           When a configured user-controlled interface is created by
+           the system, it is instantiated with the same name in the
+           /interface-state/interface list.";
+      }
+
+      leaf description {
+        type string;
+        description
+          "A textual description of the interface.
+
+           A server implementation MAY map this leaf to the ifAlias
+           MIB object.  Such an implementation needs to use some
+           mechanism to handle the differences in size and characters
+           allowed between this leaf and ifAlias.  The definition of
+           such a mechanism is outside the scope of this document.
+
+           Since ifAlias is defined to be stored in non-volatile
+           storage, the MIB implementation MUST map ifAlias to the
+           value of 'description' in the persistently stored
+           datastore.
+
+           Specifically, if the device supports ':startup', when
+           ifAlias is read the device MUST return the value of
+           'description' in the 'startup' datastore, and when it is
+           written, it MUST be written to the 'running' and 'startup'
+           datastores.  Note that it is up to the implementation to
+
+           decide whether to modify this single leaf in 'startup' or
+           perform an implicit copy-config from 'running' to
+           'startup'.
+
+           If the device does not support ':startup', ifAlias MUST
+           be mapped to the 'description' leaf in the 'running'
+           datastore.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifAlias";
+      }
+
+      leaf type {
+        type identityref {
+          base interface-type;
+        }
+        mandatory true;
+        description
+          "The type of the interface.
+
+           When an interface entry is created, a server MAY
+           initialize the type leaf with a valid value, e.g., if it
+           is possible to derive the type from the name of the
+           interface.
+
+           If a client tries to set the type of an interface to a
+           value that can never be used by the system, e.g., if the
+           type is not supported or if the type does not match the
+           name of the interface, the server MUST reject the request.
+           A NETCONF server MUST reply with an rpc-error with the
+           error-tag 'invalid-value' in this case.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifType";
+      }
+
+      leaf enabled {
+        type boolean;
+        default "true";
+        description
+          "This leaf contains the configured, desired state of the
+           interface.
+
+           Systems that implement the IF-MIB use the value of this
+           leaf in the 'running' datastore to set
+           IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
+           has been initialized, as described in RFC 2863.
+
+
+
+           Changes in this leaf in the 'running' datastore are
+           reflected in ifAdminStatus, but if ifAdminStatus is
+           changed over SNMP, this leaf is not affected.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+      }
+
+      leaf link-up-down-trap-enable {
+        if-feature if-mib;
+        type enumeration {
+          enum enabled {
+            value 1;
+          }
+          enum disabled {
+            value 2;
+          }
+        }
+        description
+          "Controls whether linkUp/linkDown SNMP notifications
+           should be generated for this interface.
+
+           If this node is not configured, the value 'enabled' is
+           operationally used by the server for interfaces that do
+           not operate on top of any other interface (i.e., there are
+           no 'lower-layer-if' entries), and 'disabled' otherwise.";
+        reference
+          "RFC 2863: The Interfaces Group MIB -
+                     ifLinkUpDownTrapEnable";
+      }
+    }
+  }
+
+  /*
+   * Operational state data nodes
+   */
+
+  container interfaces-state {
+    config false;
+    description
+      "Data nodes for the operational state of interfaces.";
+
+    list interface {
+      key "name";
+
+
+
+
+
+      description
+        "The list of interfaces on the device.
+
+         System-controlled interfaces created by the system are
+         always present in this list, whether they are configured or
+         not.";
+
+      leaf name {
+        type string;
+        description
+          "The name of the interface.
+
+           A server implementation MAY map this leaf to the ifName
+           MIB object.  Such an implementation needs to use some
+           mechanism to handle the differences in size and characters
+           allowed between this leaf and ifName.  The definition of
+           such a mechanism is outside the scope of this document.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifName";
+      }
+
+      leaf type {
+        type identityref {
+          base interface-type;
+        }
+        mandatory true;
+        description
+          "The type of the interface.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifType";
+      }
+
+      leaf admin-status {
+        if-feature if-mib;
+        type enumeration {
+          enum up {
+            value 1;
+            description
+              "Ready to pass packets.";
+          }
+          enum down {
+            value 2;
+            description
+              "Not ready to pass packets and not in some test mode.";
+          }
+
+
+
+          enum testing {
+            value 3;
+            description
+              "In some test mode.";
+          }
+        }
+        mandatory true;
+        description
+          "The desired state of the interface.
+
+           This leaf has the same read semantics as ifAdminStatus.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+      }
+
+      leaf oper-status {
+        type enumeration {
+          enum up {
+            value 1;
+            description
+              "Ready to pass packets.";
+          }
+          enum down {
+            value 2;
+            description
+              "The interface does not pass any packets.";
+          }
+          enum testing {
+            value 3;
+            description
+              "In some test mode.  No operational packets can
+               be passed.";
+          }
+          enum unknown {
+            value 4;
+            description
+              "Status cannot be determined for some reason.";
+          }
+          enum dormant {
+            value 5;
+            description
+              "Waiting for some external event.";
+          }
+          enum not-present {
+            value 6;
+            description
+              "Some component (typically hardware) is missing.";
+          }
+          enum lower-layer-down {
+            value 7;
+            description
+              "Down due to state of lower-layer interface(s).";
+          }
+        }
+        mandatory true;
+        description
+          "The current operational state of the interface.
+
+           This leaf has the same semantics as ifOperStatus.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifOperStatus";
+      }
+
+      leaf last-change {
+        type yang:date-and-time;
+        description
+          "The time the interface entered its current operational
+           state.  If the current state was entered prior to the
+           last re-initialization of the local network management
+           subsystem, then this node is not present.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifLastChange";
+      }
+
+      leaf if-index {
+        if-feature if-mib;
+        type int32 {
+          range "1..2147483647";
+        }
+        mandatory true;
+        description
+          "The ifIndex value for the ifEntry represented by this
+           interface.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifIndex";
+      }
+
+      leaf phys-address {
+        type yang:phys-address;
+        description
+          "The interface's address at its protocol sub-layer.  For
+           example, for an 802.x interface, this object normally
+           contains a Media Access Control (MAC) address.  The
+           interface's media-specific modules must define the bit
+
+
+           and byte ordering and the format of the value of this
+           object.  For interfaces that do not have such an address
+           (e.g., a serial line), this node is not present.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifPhysAddress";
+      }
+
+      leaf-list higher-layer-if {
+        type interface-state-ref;
+        description
+          "A list of references to interfaces layered on top of this
+           interface.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifStackTable";
+      }
+
+      leaf-list lower-layer-if {
+        type interface-state-ref;
+        description
+          "A list of references to interfaces layered underneath this
+           interface.";
+        reference
+          "RFC 2863: The Interfaces Group MIB - ifStackTable";
+      }
+
+      leaf speed {
+        type yang:gauge64;
+        units "bits/second";
+        description
+            "An estimate of the interface's current bandwidth in bits
+             per second.  For interfaces that do not vary in
+             bandwidth or for those where no accurate estimation can
+             be made, this node should contain the nominal bandwidth.
+             For interfaces that have no concept of bandwidth, this
+             node is not present.";
+        reference
+          "RFC 2863: The Interfaces Group MIB -
+                     ifSpeed, ifHighSpeed";
+      }
+
+
+
+
+
+
+
+
+
+      container statistics {
+        description
+          "A collection of interface-related statistics objects.";
+
+        leaf discontinuity-time {
+          type yang:date-and-time;
+          mandatory true;
+          description
+            "The time on the most recent occasion at which any one or
+             more of this interface's counters suffered a
+             discontinuity.  If no such discontinuities have occurred
+             since the last re-initialization of the local management
+             subsystem, then this node contains the time the local
+             management subsystem re-initialized itself.";
+        }
+
+        leaf in-octets {
+          type yang:counter64;
+          description
+            "The total number of octets received on the interface,
+             including framing characters.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifHCInOctets";
+        }
+
+        leaf in-unicast-pkts {
+          type yang:counter64;
+          description
+            "The number of packets, delivered by this sub-layer to a
+             higher (sub-)layer, that were not addressed to a
+             multicast or broadcast address at this sub-layer.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
+        }
+
+
+
+
+        leaf in-broadcast-pkts {
+          type yang:counter64;
+          description
+            "The number of packets, delivered by this sub-layer to a
+             higher (sub-)layer, that were addressed to a broadcast
+             address at this sub-layer.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB -
+                       ifHCInBroadcastPkts";
+        }
+
+        leaf in-multicast-pkts {
+          type yang:counter64;
+          description
+            "The number of packets, delivered by this sub-layer to a
+             higher (sub-)layer, that were addressed to a multicast
+             address at this sub-layer.  For a MAC-layer protocol,
+             this includes both Group and Functional addresses.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB -
+                       ifHCInMulticastPkts";
+        }
+
+        leaf in-discards {
+          type yang:counter32;
+          description
+            "The number of inbound packets that were chosen to be
+             discarded even though no errors had been detected to
+             prevent their being deliverable to a higher-layer
+             protocol.  One possible reason for discarding such a
+             packet could be to free up buffer space.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+
+
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifInDiscards";
+        }
+
+        leaf in-errors {
+          type yang:counter32;
+          description
+            "For packet-oriented interfaces, the number of inbound
+             packets that contained errors preventing them from being
+             deliverable to a higher-layer protocol.  For character-
+             oriented or fixed-length interfaces, the number of
+             inbound transmission units that contained errors
+             preventing them from being deliverable to a higher-layer
+             protocol.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifInErrors";
+        }
+
+        leaf in-unknown-protos {
+          type yang:counter32;
+          description
+            "For packet-oriented interfaces, the number of packets
+             received via the interface that were discarded because
+             of an unknown or unsupported protocol.  For
+             character-oriented or fixed-length interfaces that
+             support protocol multiplexing, the number of
+             transmission units received via the interface that were
+             discarded because of an unknown or unsupported protocol.
+             For any interface that does not support protocol
+             multiplexing, this counter is not present.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
+        }
+
+
+
+
+
+        leaf out-octets {
+          type yang:counter64;
+          description
+            "The total number of octets transmitted out of the
+             interface, including framing characters.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
+        }
+
+        leaf out-unicast-pkts {
+          type yang:counter64;
+          description
+            "The total number of packets that higher-level protocols
+             requested be transmitted, and that were not addressed
+             to a multicast or broadcast address at this sub-layer,
+             including those that were discarded or not sent.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
+        }
+
+        leaf out-broadcast-pkts {
+          type yang:counter64;
+          description
+            "The total number of packets that higher-level protocols
+             requested be transmitted, and that were addressed to a
+             broadcast address at this sub-layer, including those
+             that were discarded or not sent.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB -
+                       ifHCOutBroadcastPkts";
+        }
+
+
+        leaf out-multicast-pkts {
+          type yang:counter64;
+          description
+            "The total number of packets that higher-level protocols
+             requested be transmitted, and that were addressed to a
+             multicast address at this sub-layer, including those
+             that were discarded or not sent.  For a MAC-layer
+             protocol, this includes both Group and Functional
+             addresses.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB -
+                       ifHCOutMulticastPkts";
+        }
+
+        leaf out-discards {
+          type yang:counter32;
+          description
+            "The number of outbound packets that were chosen to be
+             discarded even though no errors had been detected to
+             prevent their being transmitted.  One possible reason
+             for discarding such a packet could be to free up buffer
+             space.
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifOutDiscards";
+        }
+
+        leaf out-errors {
+          type yang:counter32;
+          description
+            "For packet-oriented interfaces, the number of outbound
+             packets that could not be transmitted because of errors.
+             For character-oriented or fixed-length interfaces, the
+             number of outbound transmission units that could not be
+             transmitted because of errors.
+
+
+
+
+             Discontinuities in the value of this counter can occur
+             at re-initialization of the management system, and at
+             other times as indicated by the value of
+             'discontinuity-time'.";
+          reference
+            "RFC 2863: The Interfaces Group MIB - ifOutErrors";
+        }
+      }
+    }
+  }
+}
diff --git a/tests/yanglint/modules/ietf-ip.yang b/tests/yanglint/modules/ietf-ip.yang
new file mode 100644
index 0000000..1499120
--- /dev/null
+++ b/tests/yanglint/modules/ietf-ip.yang
@@ -0,0 +1,758 @@
+module ietf-ip {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-ip";
+ prefix ip;
+
+ import ietf-interfaces {
+   prefix if;
+ }
+ import ietf-inet-types {
+   prefix inet;
+ }
+ import ietf-yang-types {
+   prefix yang;
+ }
+
+ organization
+   "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+   "WG Web:   <http://tools.ietf.org/wg/netmod/>
+    WG List:  <mailto:netmod@ietf.org>
+
+    WG Chair: Thomas Nadeau
+              <mailto:tnadeau@lucidvision.com>
+
+    WG Chair: Juergen Schoenwaelder
+              <mailto:j.schoenwaelder@jacobs-university.de>
+
+    Editor:   Martin Bjorklund
+              <mailto:mbj@tail-f.com>";
+
+
+
+
+
+
+
+
+
+
+ description
+   "This module contains a collection of YANG definitions for
+    configuring IP implementations.
+
+    Copyright (c) 2014 IETF Trust and the persons identified as
+    authors of the code.  All rights reserved.
+
+    Redistribution and use in source and binary forms, with or
+    without modification, is permitted pursuant to, and subject
+    to the license terms contained in, the Simplified BSD License
+    set forth in Section 4.c of the IETF Trust's Legal Provisions
+    Relating to IETF Documents
+    (http://trustee.ietf.org/license-info).
+
+    This version of this YANG module is part of RFC 7277; see
+    the RFC itself for full legal notices.";
+
+ revision 2014-06-16 {
+   description
+     "Initial revision.";
+   reference
+     "RFC 7277: A YANG Data Model for IP Management";
+ }
+
+ /*
+
+  * Features
+  */
+
+ feature ipv4-non-contiguous-netmasks {
+   description
+     "Indicates support for configuring non-contiguous
+      subnet masks.";
+ }
+
+ feature ipv6-privacy-autoconf {
+   description
+     "Indicates support for Privacy Extensions for Stateless Address
+      Autoconfiguration in IPv6.";
+   reference
+     "RFC 4941: Privacy Extensions for Stateless Address
+                Autoconfiguration in IPv6";
+ }
+
+
+
+
+
+ /*
+  * Typedefs
+  */
+
+ typedef ip-address-origin {
+   type enumeration {
+     enum other {
+       description
+         "None of the following.";
+     }
+     enum static {
+       description
+         "Indicates that the address has been statically
+          configured - for example, using NETCONF or a Command Line
+          Interface.";
+     }
+     enum dhcp {
+       description
+         "Indicates an address that has been assigned to this
+          system by a DHCP server.";
+     }
+     enum link-layer {
+       description
+         "Indicates an address created by IPv6 stateless
+          autoconfiguration that embeds a link-layer address in its
+          interface identifier.";
+     }
+     enum random {
+       description
+         "Indicates an address chosen by the system at
+
+          random, e.g., an IPv4 address within 169.254/16, an
+          RFC 4941 temporary address, or an RFC 7217 semantically
+          opaque address.";
+       reference
+         "RFC 4941: Privacy Extensions for Stateless Address
+                    Autoconfiguration in IPv6
+          RFC 7217: A Method for Generating Semantically Opaque
+                    Interface Identifiers with IPv6 Stateless
+                    Address Autoconfiguration (SLAAC)";
+     }
+   }
+   description
+     "The origin of an address.";
+ }
+
+
+
+ typedef neighbor-origin {
+   type enumeration {
+     enum other {
+       description
+         "None of the following.";
+     }
+     enum static {
+       description
+         "Indicates that the mapping has been statically
+          configured - for example, using NETCONF or a Command Line
+          Interface.";
+     }
+     enum dynamic {
+       description
+         "Indicates that the mapping has been dynamically resolved
+          using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery
+          protocol.";
+     }
+   }
+   description
+     "The origin of a neighbor entry.";
+ }
+
+ /*
+  * Configuration data nodes
+  */
+
+ augment "/if:interfaces/if:interface" {
+   description
+     "Parameters for configuring IP on interfaces.
+
+      If an interface is not capable of running IP, the server
+      must not allow the client to configure these parameters.";
+
+   container ipv4 {
+     presence
+       "Enables IPv4 unless the 'enabled' leaf
+        (which defaults to 'true') is set to 'false'";
+     description
+       "Parameters for the IPv4 address family.";
+
+
+
+
+
+
+
+
+     leaf enabled {
+       type boolean;
+       default true;
+       description
+         "Controls whether IPv4 is enabled or disabled on this
+          interface.  When IPv4 is enabled, this interface is
+          connected to an IPv4 stack, and the interface can send
+          and receive IPv4 packets.";
+     }
+     leaf forwarding {
+       type boolean;
+       default false;
+       description
+         "Controls IPv4 packet forwarding of datagrams received by,
+          but not addressed to, this interface.  IPv4 routers
+          forward datagrams.  IPv4 hosts do not (except those
+          source-routed via the host).";
+     }
+     leaf mtu {
+       type uint16 {
+         range "68..max";
+       }
+       units octets;
+       description
+         "The size, in octets, of the largest IPv4 packet that the
+          interface will send and receive.
+
+          The server may restrict the allowed values for this leaf,
+          depending on the interface's type.
+
+          If this leaf is not configured, the operationally used MTU
+          depends on the interface's type.";
+       reference
+         "RFC 791: Internet Protocol";
+     }
+     list address {
+       key "ip";
+       description
+         "The list of configured IPv4 addresses on the interface.";
+
+       leaf ip {
+         type inet:ipv4-address-no-zone;
+         description
+           "The IPv4 address on the interface.";
+       }
+
+
+
+       choice subnet {
+         mandatory true;
+         description
+           "The subnet can be specified as a prefix-length, or,
+            if the server supports non-contiguous netmasks, as
+            a netmask.";
+         leaf prefix-length {
+           type uint8 {
+             range "0..32";
+           }
+           description
+             "The length of the subnet prefix.";
+         }
+         leaf netmask {
+           if-feature ipv4-non-contiguous-netmasks;
+           type yang:dotted-quad;
+           description
+             "The subnet specified as a netmask.";
+         }
+       }
+     }
+     list neighbor {
+       key "ip";
+       description
+         "A list of mappings from IPv4 addresses to
+          link-layer addresses.
+
+          Entries in this list are used as static entries in the
+          ARP Cache.";
+       reference
+         "RFC 826: An Ethernet Address Resolution Protocol";
+
+       leaf ip {
+         type inet:ipv4-address-no-zone;
+         description
+           "The IPv4 address of the neighbor node.";
+       }
+       leaf link-layer-address {
+         type yang:phys-address;
+         mandatory true;
+         description
+           "The link-layer address of the neighbor node.";
+       }
+     }
+
+   }
+
+
+   container ipv6 {
+     presence
+       "Enables IPv6 unless the 'enabled' leaf
+        (which defaults to 'true') is set to 'false'";
+     description
+       "Parameters for the IPv6 address family.";
+
+     leaf enabled {
+       type boolean;
+       default true;
+       description
+         "Controls whether IPv6 is enabled or disabled on this
+          interface.  When IPv6 is enabled, this interface is
+          connected to an IPv6 stack, and the interface can send
+          and receive IPv6 packets.";
+     }
+     leaf forwarding {
+       type boolean;
+       default false;
+       description
+         "Controls IPv6 packet forwarding of datagrams received by,
+          but not addressed to, this interface.  IPv6 routers
+          forward datagrams.  IPv6 hosts do not (except those
+          source-routed via the host).";
+       reference
+         "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+                    Section 6.2.1, IsRouter";
+     }
+     leaf mtu {
+       type uint32 {
+         range "1280..max";
+       }
+       units octets;
+       description
+         "The size, in octets, of the largest IPv6 packet that the
+          interface will send and receive.
+
+          The server may restrict the allowed values for this leaf,
+          depending on the interface's type.
+
+          If this leaf is not configured, the operationally used MTU
+          depends on the interface's type.";
+       reference
+         "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+                    Section 5";
+     }
+
+
+     list address {
+       key "ip";
+       description
+         "The list of configured IPv6 addresses on the interface.";
+
+       leaf ip {
+         type inet:ipv6-address-no-zone;
+         description
+           "The IPv6 address on the interface.";
+       }
+       leaf prefix-length {
+         type uint8 {
+           range "0..128";
+         }
+         mandatory true;
+         description
+           "The length of the subnet prefix.";
+       }
+     }
+     list neighbor {
+       key "ip";
+       description
+         "A list of mappings from IPv6 addresses to
+          link-layer addresses.
+
+          Entries in this list are used as static entries in the
+          Neighbor Cache.";
+       reference
+         "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)";
+
+       leaf ip {
+         type inet:ipv6-address-no-zone;
+         description
+           "The IPv6 address of the neighbor node.";
+       }
+       leaf link-layer-address {
+         type yang:phys-address;
+         mandatory true;
+         description
+           "The link-layer address of the neighbor node.";
+       }
+     }
+
+
+
+
+
+
+     leaf dup-addr-detect-transmits {
+       type uint32;
+       default 1;
+       description
+         "The number of consecutive Neighbor Solicitation messages
+          sent while performing Duplicate Address Detection on a
+          tentative address.  A value of zero indicates that
+          Duplicate Address Detection is not performed on
+          tentative addresses.  A value of one indicates a single
+          transmission with no follow-up retransmissions.";
+       reference
+         "RFC 4862: IPv6 Stateless Address Autoconfiguration";
+     }
+     container autoconf {
+       description
+         "Parameters to control the autoconfiguration of IPv6
+          addresses, as described in RFC 4862.";
+       reference
+         "RFC 4862: IPv6 Stateless Address Autoconfiguration";
+
+       leaf create-global-addresses {
+         type boolean;
+         default true;
+         description
+           "If enabled, the host creates global addresses as
+            described in RFC 4862.";
+         reference
+           "RFC 4862: IPv6 Stateless Address Autoconfiguration
+                      Section 5.5";
+       }
+       leaf create-temporary-addresses {
+         if-feature ipv6-privacy-autoconf;
+         type boolean;
+         default false;
+         description
+           "If enabled, the host creates temporary addresses as
+            described in RFC 4941.";
+         reference
+           "RFC 4941: Privacy Extensions for Stateless Address
+                      Autoconfiguration in IPv6";
+       }
+
+
+
+
+
+
+
+       leaf temporary-valid-lifetime {
+         if-feature ipv6-privacy-autoconf;
+         type uint32;
+         units "seconds";
+         default 604800;
+         description
+           "The time period during which the temporary address
+            is valid.";
+         reference
+           "RFC 4941: Privacy Extensions for Stateless Address
+                      Autoconfiguration in IPv6
+                      - TEMP_VALID_LIFETIME";
+       }
+       leaf temporary-preferred-lifetime {
+         if-feature ipv6-privacy-autoconf;
+         type uint32;
+         units "seconds";
+         default 86400;
+         description
+           "The time period during which the temporary address is
+            preferred.";
+         reference
+           "RFC 4941: Privacy Extensions for Stateless Address
+                      Autoconfiguration in IPv6
+                      - TEMP_PREFERRED_LIFETIME";
+       }
+     }
+   }
+ }
+
+ /*
+  * Operational state data nodes
+  */
+
+ augment "/if:interfaces-state/if:interface" {
+   description
+     "Data nodes for the operational state of IP on interfaces.";
+
+   container ipv4 {
+     presence "Present if IPv4 is enabled on this interface";
+     config false;
+     description
+       "Interface-specific parameters for the IPv4 address family.";
+
+
+
+
+
+     leaf forwarding {
+       type boolean;
+       description
+         "Indicates whether IPv4 packet forwarding is enabled or
+          disabled on this interface.";
+     }
+     leaf mtu {
+       type uint16 {
+         range "68..max";
+       }
+       units octets;
+       description
+         "The size, in octets, of the largest IPv4 packet that the
+          interface will send and receive.";
+       reference
+         "RFC 791: Internet Protocol";
+     }
+     list address {
+       key "ip";
+       description
+         "The list of IPv4 addresses on the interface.";
+
+       leaf ip {
+         type inet:ipv4-address-no-zone;
+         description
+           "The IPv4 address on the interface.";
+       }
+       choice subnet {
+         description
+           "The subnet can be specified as a prefix-length, or,
+            if the server supports non-contiguous netmasks, as
+            a netmask.";
+         leaf prefix-length {
+           type uint8 {
+             range "0..32";
+           }
+           description
+             "The length of the subnet prefix.";
+         }
+         leaf netmask {
+           if-feature ipv4-non-contiguous-netmasks;
+           type yang:dotted-quad;
+           description
+             "The subnet specified as a netmask.";
+         }
+       }
+
+
+       leaf origin {
+         type ip-address-origin;
+         description
+           "The origin of this address.";
+       }
+     }
+     list neighbor {
+       key "ip";
+       description
+         "A list of mappings from IPv4 addresses to
+          link-layer addresses.
+
+          This list represents the ARP Cache.";
+       reference
+         "RFC 826: An Ethernet Address Resolution Protocol";
+
+       leaf ip {
+         type inet:ipv4-address-no-zone;
+         description
+           "The IPv4 address of the neighbor node.";
+       }
+       leaf link-layer-address {
+         type yang:phys-address;
+         description
+           "The link-layer address of the neighbor node.";
+       }
+       leaf origin {
+         type neighbor-origin;
+         description
+           "The origin of this neighbor entry.";
+       }
+     }
+
+   }
+
+   container ipv6 {
+     presence "Present if IPv6 is enabled on this interface";
+     config false;
+     description
+       "Parameters for the IPv6 address family.";
+
+
+
+
+
+
+
+
+     leaf forwarding {
+       type boolean;
+       default false;
+       description
+         "Indicates whether IPv6 packet forwarding is enabled or
+          disabled on this interface.";
+       reference
+         "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+                    Section 6.2.1, IsRouter";
+     }
+     leaf mtu {
+       type uint32 {
+         range "1280..max";
+       }
+       units octets;
+       description
+         "The size, in octets, of the largest IPv6 packet that the
+          interface will send and receive.";
+       reference
+         "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+                    Section 5";
+     }
+     list address {
+       key "ip";
+       description
+         "The list of IPv6 addresses on the interface.";
+
+       leaf ip {
+         type inet:ipv6-address-no-zone;
+         description
+           "The IPv6 address on the interface.";
+       }
+       leaf prefix-length {
+         type uint8 {
+           range "0..128";
+         }
+         mandatory true;
+         description
+           "The length of the subnet prefix.";
+       }
+       leaf origin {
+         type ip-address-origin;
+         description
+           "The origin of this address.";
+       }
+
+
+
+       leaf status {
+         type enumeration {
+           enum preferred {
+             description
+               "This is a valid address that can appear as the
+                destination or source address of a packet.";
+           }
+           enum deprecated {
+             description
+               "This is a valid but deprecated address that should
+                no longer be used as a source address in new
+                communications, but packets addressed to such an
+                address are processed as expected.";
+           }
+           enum invalid {
+             description
+               "This isn't a valid address, and it shouldn't appear
+                as the destination or source address of a packet.";
+           }
+           enum inaccessible {
+             description
+               "The address is not accessible because the interface
+                to which this address is assigned is not
+                operational.";
+           }
+           enum unknown {
+             description
+               "The status cannot be determined for some reason.";
+           }
+           enum tentative {
+             description
+               "The uniqueness of the address on the link is being
+                verified.  Addresses in this state should not be
+                used for general communication and should only be
+                used to determine the uniqueness of the address.";
+           }
+           enum duplicate {
+             description
+               "The address has been determined to be non-unique on
+                the link and so must not be used.";
+           }
+
+
+
+
+
+
+
+           enum optimistic {
+             description
+               "The address is available for use, subject to
+                restrictions, while its uniqueness on a link is
+                being verified.";
+           }
+         }
+         description
+           "The status of an address.  Most of the states correspond
+            to states from the IPv6 Stateless Address
+            Autoconfiguration protocol.";
+         reference
+           "RFC 4293: Management Information Base for the
+                      Internet Protocol (IP)
+                      - IpAddressStatusTC
+            RFC 4862: IPv6 Stateless Address Autoconfiguration";
+       }
+     }
+     list neighbor {
+       key "ip";
+       description
+         "A list of mappings from IPv6 addresses to
+          link-layer addresses.
+
+          This list represents the Neighbor Cache.";
+       reference
+         "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)";
+
+       leaf ip {
+         type inet:ipv6-address-no-zone;
+         description
+           "The IPv6 address of the neighbor node.";
+       }
+       leaf link-layer-address {
+         type yang:phys-address;
+         description
+           "The link-layer address of the neighbor node.";
+       }
+       leaf origin {
+         type neighbor-origin;
+         description
+           "The origin of this neighbor entry.";
+       }
+       leaf is-router {
+         type empty;
+         description
+           "Indicates that the neighbor node acts as a router.";
+       }
+       leaf state {
+         type enumeration {
+           enum incomplete {
+             description
+               "Address resolution is in progress, and the link-layer
+                address of the neighbor has not yet been
+                determined.";
+           }
+           enum reachable {
+             description
+               "Roughly speaking, the neighbor is known to have been
+                reachable recently (within tens of seconds ago).";
+           }
+           enum stale {
+             description
+               "The neighbor is no longer known to be reachable, but
+                until traffic is sent to the neighbor no attempt
+                should be made to verify its reachability.";
+           }
+           enum delay {
+             description
+               "The neighbor is no longer known to be reachable, and
+                traffic has recently been sent to the neighbor.
+                Rather than probe the neighbor immediately, however,
+                delay sending probes for a short while in order to
+                give upper-layer protocols a chance to provide
+                reachability confirmation.";
+           }
+           enum probe {
+             description
+               "The neighbor is no longer known to be reachable, and
+                unicast Neighbor Solicitation probes are being sent
+                to verify reachability.";
+           }
+         }
+         description
+           "The Neighbor Unreachability Detection state of this
+            entry.";
+         reference
+           "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)
+                      Section 7.3.2";
+       }
+     }
+   }
+ }
+}
diff --git a/tests/yanglint/modules/ietf-netconf-acm.yang b/tests/yanglint/modules/ietf-netconf-acm.yang
new file mode 100644
index 0000000..d372fa0
--- /dev/null
+++ b/tests/yanglint/modules/ietf-netconf-acm.yang
@@ -0,0 +1,411 @@
+module ietf-netconf-acm {
+  namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
+  prefix nacm;
+
+  import ietf-yang-types {
+    prefix yang;
+  }
+
+  organization
+    "IETF NETCONF (Network Configuration) Working Group";
+  contact
+    "WG Web:   <http://tools.ietf.org/wg/netconf/>
+     WG List:  <mailto:netconf@ietf.org>
+
+     WG Chair: Mehmet Ersue
+               <mailto:mehmet.ersue@nsn.com>
+
+     WG Chair: Bert Wijnen
+               <mailto:bertietf@bwijnen.net>
+
+     Editor:   Andy Bierman
+               <mailto:andy@yumaworks.com>
+
+     Editor:   Martin Bjorklund
+               <mailto:mbj@tail-f.com>";
+  description
+    "NETCONF Access Control Model.
+
+     Copyright (c) 2012 IETF Trust and the persons identified as
+     authors of the code.  All rights reserved.
+
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD
+     License set forth in Section 4.c of the IETF Trust's
+     Legal Provisions Relating to IETF Documents
+     (http://trustee.ietf.org/license-info).
+
+     This version of this YANG module is part of RFC 6536; see
+     the RFC itself for full legal notices.";
+
+  revision 2012-02-22 {
+    description
+      "Initial version";
+    reference
+      "RFC 6536: Network Configuration Protocol (NETCONF)
+                 Access Control Model";
+  }
+
+  extension default-deny-write {
+    description
+      "Used to indicate that the data model node
+       represents a sensitive security system parameter.
+
+       If present, and the NACM module is enabled (i.e.,
+       /nacm/enable-nacm object equals 'true'), the NETCONF server
+       will only allow the designated 'recovery session' to have
+       write access to the node.  An explicit access control rule is
+       required for all other users.
+
+       The 'default-deny-write' extension MAY appear within a data
+       definition statement.  It is ignored otherwise.";
+  }
+
+  extension default-deny-all {
+    description
+      "Used to indicate that the data model node
+       controls a very sensitive security system parameter.
+
+       If present, and the NACM module is enabled (i.e.,
+       /nacm/enable-nacm object equals 'true'), the NETCONF server
+       will only allow the designated 'recovery session' to have
+       read, write, or execute access to the node.  An explicit
+       access control rule is required for all other users.
+
+       The 'default-deny-all' extension MAY appear within a data
+       definition statement, 'rpc' statement, or 'notification'
+       statement.  It is ignored otherwise.";
+  }
+
+  typedef user-name-type {
+    type string {
+      length "1..max";
+    }
+    description
+      "General Purpose Username string.";
+  }
+
+  typedef matchall-string-type {
+    type string {
+      pattern "\\*";
+    }
+    description
+      "The string containing a single asterisk '*' is used
+       to conceptually represent all possible values
+       for the particular leaf using this data type.";
+  }
+
+  typedef access-operations-type {
+    type bits {
+      bit create {
+        description
+          "Any protocol operation that creates a
+           new data node.";
+      }
+      bit read {
+        description
+          "Any protocol operation or notification that
+           returns the value of a data node.";
+      }
+      bit update {
+        description
+          "Any protocol operation that alters an existing
+           data node.";
+      }
+      bit delete {
+        description
+          "Any protocol operation that removes a data node.";
+      }
+      bit exec {
+        description
+          "Execution access to the specified protocol operation.";
+      }
+    }
+    description
+      "NETCONF Access Operation.";
+  }
+
+  typedef group-name-type {
+    type string {
+      length "1..max";
+      pattern "[^\\*].*";
+    }
+    description
+      "Name of administrative group to which
+       users can be assigned.";
+  }
+
+  typedef action-type {
+    type enumeration {
+      enum "permit" {
+        description
+          "Requested action is permitted.";
+      }
+      enum "deny" {
+        description
+          "Requested action is denied.";
+      }
+    }
+    description
+      "Action taken by the server when a particular
+       rule matches.";
+  }
+
+  typedef node-instance-identifier {
+    type yang:xpath1.0;
+    description
+      "Path expression used to represent a special
+       data node instance identifier string.
+
+       A node-instance-identifier value is an
+       unrestricted YANG instance-identifier expression.
+       All the same rules as an instance-identifier apply
+       except predicates for keys are optional.  If a key
+       predicate is missing, then the node-instance-identifier
+       represents all possible server instances for that key.
+
+       This XPath expression is evaluated in the following context:
+
+        o  The set of namespace declarations are those in scope on
+           the leaf element where this type is used.
+
+        o  The set of variable bindings contains one variable,
+           'USER', which contains the name of the user of the current
+            session.
+
+        o  The function library is the core function library, but
+           note that due to the syntax restrictions of an
+           instance-identifier, no functions are allowed.
+
+        o  The context node is the root node in the data tree.";
+  }
+
+  container nacm {
+    nacm:default-deny-all;
+    description
+      "Parameters for NETCONF Access Control Model.";
+    leaf enable-nacm {
+      type boolean;
+      default "true";
+      description
+        "Enables or disables all NETCONF access control
+         enforcement.  If 'true', then enforcement
+         is enabled.  If 'false', then enforcement
+         is disabled.";
+    }
+    leaf read-default {
+      type action-type;
+      default "permit";
+      description
+        "Controls whether read access is granted if
+         no appropriate rule is found for a
+         particular read request.";
+    }
+    leaf write-default {
+      type action-type;
+      default "deny";
+      description
+        "Controls whether create, update, or delete access
+         is granted if no appropriate rule is found for a
+         particular write request.";
+    }
+    leaf exec-default {
+      type action-type;
+      default "permit";
+      description
+        "Controls whether exec access is granted if no appropriate
+         rule is found for a particular protocol operation request.";
+    }
+    leaf enable-external-groups {
+      type boolean;
+      default "true";
+      description
+        "Controls whether the server uses the groups reported by the
+         NETCONF transport layer when it assigns the user to a set of
+         NACM groups.  If this leaf has the value 'false', any group
+         names reported by the transport layer are ignored by the
+         server.";
+    }
+    leaf denied-operations {
+      type yang:zero-based-counter32;
+      config false;
+      mandatory true;
+      description
+        "Number of times since the server last restarted that a
+         protocol operation request was denied.";
+    }
+    leaf denied-data-writes {
+      type yang:zero-based-counter32;
+      config false;
+      mandatory true;
+      description
+        "Number of times since the server last restarted that a
+         protocol operation request to alter
+         a configuration datastore was denied.";
+    }
+    leaf denied-notifications {
+      type yang:zero-based-counter32;
+      config false;
+      mandatory true;
+      description
+        "Number of times since the server last restarted that
+         a notification was dropped for a subscription because
+         access to the event type was denied.";
+    }
+    container groups {
+      description
+        "NETCONF Access Control Groups.";
+      list group {
+        key "name";
+        description
+          "One NACM Group Entry.  This list will only contain
+           configured entries, not any entries learned from
+           any transport protocols.";
+        leaf name {
+          type group-name-type;
+          description
+            "Group name associated with this entry.";
+        }
+        leaf-list user-name {
+          type user-name-type;
+          description
+            "Each entry identifies the username of
+             a member of the group associated with
+             this entry.";
+        }
+      }
+    }
+    list rule-list {
+      key "name";
+      ordered-by user;
+      description
+        "An ordered collection of access control rules.";
+      leaf name {
+        type string {
+          length "1..max";
+        }
+        description
+          "Arbitrary name assigned to the rule-list.";
+      }
+      leaf-list group {
+        type union {
+          type matchall-string-type;
+          type group-name-type;
+        }
+        description
+          "List of administrative groups that will be
+           assigned the associated access rights
+           defined by the 'rule' list.
+
+           The string '*' indicates that all groups apply to the
+           entry.";
+      }
+      list rule {
+        key "name";
+        ordered-by user;
+        description
+          "One access control rule.
+
+           Rules are processed in user-defined order until a match is
+           found.  A rule matches if 'module-name', 'rule-type', and
+           'access-operations' match the request.  If a rule
+           matches, the 'action' leaf determines if access is granted
+           or not.";
+        leaf name {
+          type string {
+            length "1..max";
+          }
+          description
+            "Arbitrary name assigned to the rule.";
+        }
+        leaf module-name {
+          type union {
+            type matchall-string-type;
+            type string;
+          }
+          default "*";
+          description
+            "Name of the module associated with this rule.
+
+             This leaf matches if it has the value '*' or if the
+             object being accessed is defined in the module with the
+             specified module name.";
+        }
+        choice rule-type {
+          description
+            "This choice matches if all leafs present in the rule
+             match the request.  If no leafs are present, the
+             choice matches all requests.";
+          case protocol-operation {
+            leaf rpc-name {
+              type union {
+                type matchall-string-type;
+                type string;
+              }
+              description
+                "This leaf matches if it has the value '*' or if
+                 its value equals the requested protocol operation
+                 name.";
+            }
+          }
+          case notification {
+            leaf notification-name {
+              type union {
+                type matchall-string-type;
+                type string;
+              }
+              description
+                "This leaf matches if it has the value '*' or if its
+                 value equals the requested notification name.";
+            }
+          }
+          case data-node {
+            leaf path {
+              type node-instance-identifier;
+              mandatory true;
+              description
+                "Data Node Instance Identifier associated with the
+                 data node controlled by this rule.
+
+                 Configuration data or state data instance
+                 identifiers start with a top-level data node.  A
+                 complete instance identifier is required for this
+                 type of path value.
+
+                 The special value '/' refers to all possible
+                 datastore contents.";
+            }
+          }
+        }
+        leaf access-operations {
+          type union {
+            type matchall-string-type;
+            type access-operations-type;
+          }
+          default "*";
+          description
+            "Access operations associated with this rule.
+
+             This leaf matches if it has the value '*' or if the
+             bit corresponding to the requested operation is set.";
+        }
+        leaf action {
+          type action-type;
+          mandatory true;
+          description
+            "The access control action associated with the
+             rule.  If a rule is determined to match a
+             particular request, then this object is used
+             to determine whether to permit or deny the
+             request.";
+        }
+        leaf comment {
+          type string;
+          description
+            "A textual description of the access rule.";
+        }
+      }
+    }
+  }
+}
diff --git a/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang b/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang
new file mode 100644
index 0000000..e19d2b3
--- /dev/null
+++ b/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang
@@ -0,0 +1,140 @@
+module ietf-netconf-with-defaults {
+
+   namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults";
+
+   prefix ncwd;
+
+   import ietf-netconf { prefix nc; }
+
+   organization
+    "IETF NETCONF (Network Configuration Protocol) Working Group";
+
+   contact
+    "WG Web:   <http://tools.ietf.org/wg/netconf/>
+
+     WG List:  <netconf@ietf.org>
+
+     WG Chair: Bert Wijnen
+               <bertietf@bwijnen.net>
+
+     WG Chair: Mehmet Ersue
+               <mehmet.ersue@nsn.com>
+
+     Editor: Andy Bierman
+             <andy.bierman@brocade.com>
+
+     Editor: Balazs Lengyel
+             <balazs.lengyel@ericsson.com>";
+
+   description
+    "This module defines an extension to the NETCONF protocol
+     that allows the NETCONF client to control how default
+     values are handled by the server in particular NETCONF
+     operations.
+
+     Copyright (c) 2011 IETF Trust and the persons identified as
+     the document authors.  All rights reserved.
+
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD License
+     set forth in Section 4.c of the IETF Trust's Legal Provisions
+     Relating to IETF Documents
+     (http://trustee.ietf.org/license-info).
+
+     This version of this YANG module is part of RFC 6243; see
+     the RFC itself for full legal notices.";
+
+   revision 2011-06-01 {
+     description
+       "Initial version.";
+     reference
+      "RFC 6243: With-defaults Capability for NETCONF";
+   }
+
+   typedef with-defaults-mode {
+      description
+        "Possible modes to report default data.";
+      reference
+         "RFC 6243; Section 3.";
+      type enumeration {
+         enum report-all {
+             description
+               "All default data is reported.";
+             reference
+               "RFC 6243; Section 3.1";
+         }
+         enum report-all-tagged {
+             description
+               "All default data is reported.
+                Any nodes considered to be default data
+                will contain a 'default' XML attribute,
+                set to 'true' or '1'.";
+             reference
+               "RFC 6243; Section 3.4";
+         }
+         enum trim {
+             description
+               "Values are not reported if they contain the default.";
+             reference
+               "RFC 6243; Section 3.2";
+         }
+         enum explicit {
+             description
+               "Report values that contain the definition of
+                explicitly set data.";
+             reference
+               "RFC 6243; Section 3.3";
+         }
+     }
+   }
+
+   grouping with-defaults-parameters {
+     description
+       "Contains the <with-defaults> parameter for control
+        of defaults in NETCONF retrieval operations.";
+
+     leaf with-defaults {
+       description
+         "The explicit defaults processing mode requested.";
+       reference
+         "RFC 6243; Section 4.5.1";
+
+       type with-defaults-mode;
+     }
+   }
+
+   // extending the get-config operation
+   augment /nc:get-config/nc:input {
+       description
+         "Adds the <with-defaults> parameter to the
+          input of the NETCONF <get-config> operation.";
+       reference
+         "RFC 6243; Section 4.5.1";
+
+       uses with-defaults-parameters;
+   }
+
+   // extending the get operation
+   augment /nc:get/nc:input {
+       description
+         "Adds the <with-defaults> parameter to
+          the input of the NETCONF <get> operation.";
+       reference
+         "RFC 6243; Section 4.5.1";
+
+       uses with-defaults-parameters;
+   }
+
+   // extending the copy-config operation
+   augment /nc:copy-config/nc:input {
+       description
+         "Adds the <with-defaults> parameter to
+          the input of the NETCONF <copy-config> operation.";
+       reference
+         "RFC 6243; Section 4.5.1";
+
+       uses with-defaults-parameters;
+   }
+
+}
diff --git a/tests/yanglint/modules/ietf-netconf@2011-06-01.yang b/tests/yanglint/modules/ietf-netconf@2011-06-01.yang
new file mode 100644
index 0000000..3053db2
--- /dev/null
+++ b/tests/yanglint/modules/ietf-netconf@2011-06-01.yang
@@ -0,0 +1,934 @@
+module ietf-netconf {
+
+  // the namespace for NETCONF XML definitions is unchanged
+  // from RFC 4741, which this document replaces
+  namespace "urn:ietf:params:xml:ns:netconf:base:1.0";
+
+  prefix nc;
+
+  import ietf-inet-types {
+    prefix inet;
+  }
+
+  import ietf-netconf-acm { prefix nacm; }
+
+  organization
+    "IETF NETCONF (Network Configuration) Working Group";
+
+  contact
+    "WG Web:   <http://tools.ietf.org/wg/netconf/>
+     WG List:  <netconf@ietf.org>
+
+     WG Chair: Bert Wijnen
+               <bertietf@bwijnen.net>
+
+     WG Chair: Mehmet Ersue
+               <mehmet.ersue@nsn.com>
+
+     Editor:   Martin Bjorklund
+               <mbj@tail-f.com>
+
+     Editor:   Juergen Schoenwaelder
+               <j.schoenwaelder@jacobs-university.de>
+
+     Editor:   Andy Bierman
+               <andy.bierman@brocade.com>";
+  description
+    "NETCONF Protocol Data Types and Protocol Operations.
+
+     Copyright (c) 2011 IETF Trust and the persons identified as
+     the document authors.  All rights reserved.
+
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD License
+     set forth in Section 4.c of the IETF Trust's Legal Provisions
+     Relating to IETF Documents
+     (http://trustee.ietf.org/license-info).
+
+     This version of this YANG module is part of RFC 6241; see
+     the RFC itself for full legal notices.";
+
+  revision 2011-06-01 {
+    description
+      "Initial revision;
+       2013-09-29: Updated to include NACM attributes,
+       as specified in RFC 6536: sec 3.2.5 and 3.2.8";
+    reference
+      "RFC 6241: Network Configuration Protocol";
+  }
+
+  extension get-filter-element-attributes {
+    description
+      "If this extension is present within an 'anyxml'
+       statement named 'filter', which must be conceptually
+       defined within the RPC input section for the <get>
+       and <get-config> protocol operations, then the
+       following unqualified XML attribute is supported
+       within the <filter> element, within a <get> or
+       <get-config> protocol operation:
+
+         type : optional attribute with allowed
+                value strings 'subtree' and 'xpath'.
+                If missing, the default value is 'subtree'.
+
+       If the 'xpath' feature is supported, then the
+       following unqualified XML attribute is
+       also supported:
+
+         select: optional attribute containing a
+                 string representing an XPath expression.
+                 The 'type' attribute must be equal to 'xpath'
+                 if this attribute is present.";
+  }
+
+  // NETCONF capabilities defined as features
+  feature writable-running {
+    description
+      "NETCONF :writable-running capability;
+       If the server advertises the :writable-running
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.2";
+  }
+
+  feature candidate {
+    description
+      "NETCONF :candidate capability;
+       If the server advertises the :candidate
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.3";
+  }
+
+  feature confirmed-commit {
+    if-feature candidate;
+    description
+      "NETCONF :confirmed-commit:1.1 capability;
+       If the server advertises the :confirmed-commit:1.1
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+
+    reference "RFC 6241, Section 8.4";
+  }
+
+  feature rollback-on-error {
+    description
+      "NETCONF :rollback-on-error capability;
+       If the server advertises the :rollback-on-error
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.5";
+  }
+
+  feature validate {
+    description
+      "NETCONF :validate:1.1 capability;
+       If the server advertises the :validate:1.1
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.6";
+  }
+
+  feature startup {
+    description
+      "NETCONF :startup capability;
+       If the server advertises the :startup
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.7";
+  }
+
+  feature url {
+    description
+      "NETCONF :url capability;
+       If the server advertises the :url
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.8";
+  }
+
+  feature xpath {
+    description
+      "NETCONF :xpath capability;
+       If the server advertises the :xpath
+       capability for a session, then this feature must
+       also be enabled for that session.  Otherwise,
+       this feature must not be enabled.";
+    reference "RFC 6241, Section 8.9";
+  }
+
+  // NETCONF Simple Types
+
+  typedef session-id-type {
+    type uint32 {
+      range "1..max";
+    }
+    description
+      "NETCONF Session Id";
+  }
+
+  typedef session-id-or-zero-type {
+    type uint32;
+    description
+      "NETCONF Session Id or Zero to indicate none";
+  }
+  typedef error-tag-type {
+    type enumeration {
+       enum in-use {
+         description
+           "The request requires a resource that
+            already is in use.";
+       }
+       enum invalid-value {
+         description
+           "The request specifies an unacceptable value for one
+            or more parameters.";
+       }
+       enum too-big {
+         description
+           "The request or response (that would be generated) is
+            too large for the implementation to handle.";
+       }
+       enum missing-attribute {
+         description
+           "An expected attribute is missing.";
+       }
+       enum bad-attribute {
+         description
+           "An attribute value is not correct; e.g., wrong type,
+            out of range, pattern mismatch.";
+       }
+       enum unknown-attribute {
+         description
+           "An unexpected attribute is present.";
+       }
+       enum missing-element {
+         description
+           "An expected element is missing.";
+       }
+       enum bad-element {
+         description
+           "An element value is not correct; e.g., wrong type,
+            out of range, pattern mismatch.";
+       }
+       enum unknown-element {
+         description
+           "An unexpected element is present.";
+       }
+       enum unknown-namespace {
+         description
+           "An unexpected namespace is present.";
+       }
+       enum access-denied {
+         description
+           "Access to the requested protocol operation or
+            data model is denied because authorization failed.";
+       }
+       enum lock-denied {
+         description
+           "Access to the requested lock is denied because the
+            lock is currently held by another entity.";
+       }
+       enum resource-denied {
+         description
+           "Request could not be completed because of
+            insufficient resources.";
+       }
+       enum rollback-failed {
+         description
+           "Request to roll back some configuration change (via
+            rollback-on-error or <discard-changes> operations)
+            was not completed for some reason.";
+
+       }
+       enum data-exists {
+         description
+           "Request could not be completed because the relevant
+            data model content already exists.  For example,
+            a 'create' operation was attempted on data that
+            already exists.";
+       }
+       enum data-missing {
+         description
+           "Request could not be completed because the relevant
+            data model content does not exist.  For example,
+            a 'delete' operation was attempted on
+            data that does not exist.";
+       }
+       enum operation-not-supported {
+         description
+           "Request could not be completed because the requested
+            operation is not supported by this implementation.";
+       }
+       enum operation-failed {
+         description
+           "Request could not be completed because the requested
+            operation failed for some reason not covered by
+            any other error condition.";
+       }
+       enum partial-operation {
+         description
+           "This error-tag is obsolete, and SHOULD NOT be sent
+            by servers conforming to this document.";
+       }
+       enum malformed-message {
+         description
+           "A message could not be handled because it failed to
+            be parsed correctly.  For example, the message is not
+            well-formed XML or it uses an invalid character set.";
+       }
+     }
+     description "NETCONF Error Tag";
+     reference "RFC 6241, Appendix A";
+  }
+
+  typedef error-severity-type {
+    type enumeration {
+      enum error {
+        description "Error severity";
+      }
+      enum warning {
+        description "Warning severity";
+      }
+    }
+    description "NETCONF Error Severity";
+    reference "RFC 6241, Section 4.3";
+  }
+
+  typedef edit-operation-type {
+    type enumeration {
+      enum merge {
+        description
+          "The configuration data identified by the
+           element containing this attribute is merged
+           with the configuration at the corresponding
+           level in the configuration datastore identified
+           by the target parameter.";
+      }
+      enum replace {
+        description
+          "The configuration data identified by the element
+           containing this attribute replaces any related
+           configuration in the configuration datastore
+           identified by the target parameter.  If no such
+           configuration data exists in the configuration
+           datastore, it is created.  Unlike a
+           <copy-config> operation, which replaces the
+           entire target configuration, only the configuration
+           actually present in the config parameter is affected.";
+      }
+      enum create {
+        description
+          "The configuration data identified by the element
+           containing this attribute is added to the
+           configuration if and only if the configuration
+           data does not already exist in the configuration
+           datastore.  If the configuration data exists, an
+           <rpc-error> element is returned with an
+           <error-tag> value of 'data-exists'.";
+      }
+      enum delete {
+        description
+          "The configuration data identified by the element
+           containing this attribute is deleted from the
+           configuration if and only if the configuration
+           data currently exists in the configuration
+           datastore.  If the configuration data does not
+           exist, an <rpc-error> element is returned with
+           an <error-tag> value of 'data-missing'.";
+      }
+      enum remove {
+        description
+          "The configuration data identified by the element
+           containing this attribute is deleted from the
+           configuration if the configuration
+           data currently exists in the configuration
+           datastore.  If the configuration data does not
+           exist, the 'remove' operation is silently ignored
+           by the server.";
+      }
+    }
+    default "merge";
+    description "NETCONF 'operation' attribute values";
+    reference "RFC 6241, Section 7.2";
+  }
+
+  // NETCONF Standard Protocol Operations
+
+  rpc get-config {
+    description
+      "Retrieve all or part of a specified configuration.";
+
+    reference "RFC 6241, Section 7.1";
+
+    input {
+      container source {
+        description
+          "Particular configuration to retrieve.";
+
+        choice config-source {
+          mandatory true;
+          description
+            "The configuration to retrieve.";
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config source.";
+          }
+          leaf running {
+            type empty;
+            description
+              "The running configuration is the config source.";
+          }
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config source.
+               This is optional-to-implement on the server because
+               not all servers will support filtering for this
+               datastore.";
+          }
+        }
+      }
+
+      anyxml filter {
+        description
+          "Subtree or XPath filter to use.";
+        nc:get-filter-element-attributes;
+      }
+    }
+
+    output {
+      anyxml data {
+        description
+          "Copy of the source datastore subset that matched
+           the filter criteria (if any).  An empty data container
+           indicates that the request did not produce any results.";
+      }
+    }
+  }
+
+  rpc edit-config {
+    description
+      "The <edit-config> operation loads all or part of a specified
+       configuration to the specified target configuration.";
+
+    reference "RFC 6241, Section 7.2";
+
+    input {
+      container target {
+        description
+          "Particular configuration to edit.";
+
+        choice config-target {
+          mandatory true;
+          description
+            "The configuration target.";
+
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config target.";
+          }
+          leaf running {
+            if-feature writable-running;
+            type empty;
+            description
+              "The running configuration is the config source.";
+          }
+        }
+      }
+
+      leaf default-operation {
+        type enumeration {
+          enum merge {
+            description
+              "The default operation is merge.";
+          }
+          enum replace {
+            description
+              "The default operation is replace.";
+          }
+          enum none {
+            description
+              "There is no default operation.";
+          }
+        }
+        default "merge";
+        description
+          "The default operation to use.";
+      }
+
+      leaf test-option {
+        if-feature validate;
+        type enumeration {
+          enum test-then-set {
+            description
+              "The server will test and then set if no errors.";
+          }
+          enum set {
+            description
+              "The server will set without a test first.";
+          }
+
+          enum test-only {
+            description
+              "The server will only test and not set, even
+               if there are no errors.";
+          }
+        }
+        default "test-then-set";
+        description
+          "The test option to use.";
+      }
+
+      leaf error-option {
+        type enumeration {
+          enum stop-on-error {
+            description
+              "The server will stop on errors.";
+          }
+          enum continue-on-error {
+            description
+              "The server may continue on errors.";
+          }
+          enum rollback-on-error {
+            description
+              "The server will roll back on errors.
+               This value can only be used if the 'rollback-on-error'
+               feature is supported.";
+          }
+        }
+        default "stop-on-error";
+        description
+          "The error option to use.";
+      }
+
+      choice edit-content {
+        mandatory true;
+        description
+          "The content for the edit operation.";
+
+        anyxml config {
+          description
+            "Inline Config content.";
+        }
+        leaf url {
+          if-feature url;
+          type inet:uri;
+          description
+            "URL-based config content.";
+        }
+      }
+    }
+  }
+
+  rpc copy-config {
+    description
+      "Create or replace an entire configuration datastore with the
+       contents of another complete configuration datastore.";
+
+    reference "RFC 6241, Section 7.3";
+
+    input {
+      container target {
+        description
+          "Particular configuration to copy to.";
+
+        choice config-target {
+          mandatory true;
+          description
+            "The configuration target of the copy operation.";
+
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config target.";
+          }
+          leaf running {
+            if-feature writable-running;
+            type empty;
+            description
+              "The running configuration is the config target.
+               This is optional-to-implement on the server.";
+          }
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config target.";
+          }
+          leaf url {
+            if-feature url;
+            type inet:uri;
+            description
+              "The URL-based configuration is the config target.";
+          }
+        }
+      }
+
+      container source {
+        description
+          "Particular configuration to copy from.";
+
+        choice config-source {
+          mandatory true;
+          description
+            "The configuration source for the copy operation.";
+
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config source.";
+          }
+          leaf running {
+            type empty;
+            description
+              "The running configuration is the config source.";
+          }
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config source.";
+          }
+          leaf url {
+            if-feature url;
+            type inet:uri;
+            description
+              "The URL-based configuration is the config source.";
+          }
+          anyxml config {
+            description
+              "Inline Config content: <config> element.  Represents
+               an entire configuration datastore, not
+               a subset of the running datastore.";
+          }
+        }
+      }
+    }
+  }
+
+  rpc delete-config {
+    nacm:default-deny-all;
+    description
+      "Delete a configuration datastore.";
+
+    reference "RFC 6241, Section 7.4";
+
+    input {
+      container target {
+        description
+          "Particular configuration to delete.";
+
+        choice config-target {
+          mandatory true;
+          description
+            "The configuration target to delete.";
+
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config target.";
+          }
+          leaf url {
+            if-feature url;
+            type inet:uri;
+            description
+              "The URL-based configuration is the config target.";
+          }
+        }
+      }
+    }
+  }
+
+  rpc lock {
+    description
+      "The lock operation allows the client to lock the configuration
+       system of a device.";
+
+    reference "RFC 6241, Section 7.5";
+
+    input {
+      container target {
+        description
+          "Particular configuration to lock.";
+
+        choice config-target {
+          mandatory true;
+          description
+            "The configuration target to lock.";
+
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config target.";
+          }
+          leaf running {
+            type empty;
+            description
+              "The running configuration is the config target.";
+          }
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config target.";
+          }
+        }
+      }
+    }
+  }
+
+  rpc unlock {
+    description
+      "The unlock operation is used to release a configuration lock,
+       previously obtained with the 'lock' operation.";
+
+    reference "RFC 6241, Section 7.6";
+
+    input {
+      container target {
+        description
+          "Particular configuration to unlock.";
+
+        choice config-target {
+          mandatory true;
+          description
+            "The configuration target to unlock.";
+
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config target.";
+          }
+          leaf running {
+            type empty;
+            description
+              "The running configuration is the config target.";
+          }
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config target.";
+          }
+        }
+      }
+    }
+  }
+
+  rpc get {
+    description
+      "Retrieve running configuration and device state information.";
+
+    reference "RFC 6241, Section 7.7";
+
+    input {
+      anyxml filter {
+        description
+          "This parameter specifies the portion of the system
+           configuration and state data to retrieve.";
+        nc:get-filter-element-attributes;
+      }
+    }
+
+    output {
+      anyxml data {
+        description
+          "Copy of the running datastore subset and/or state
+           data that matched the filter criteria (if any).
+           An empty data container indicates that the request did not
+           produce any results.";
+      }
+    }
+  }
+
+  rpc close-session {
+    description
+      "Request graceful termination of a NETCONF session.";
+
+    reference "RFC 6241, Section 7.8";
+  }
+
+  rpc kill-session {
+    nacm:default-deny-all;
+    description
+      "Force the termination of a NETCONF session.";
+
+    reference "RFC 6241, Section 7.9";
+
+    input {
+      leaf session-id {
+        type session-id-type;
+        mandatory true;
+        description
+          "Particular session to kill.";
+      }
+    }
+  }
+
+  rpc commit {
+    if-feature candidate;
+
+    description
+      "Commit the candidate configuration as the device's new
+       current configuration.";
+
+    reference "RFC 6241, Section 8.3.4.1";
+
+    input {
+      leaf confirmed {
+        if-feature confirmed-commit;
+        type empty;
+        description
+          "Requests a confirmed commit.";
+        reference "RFC 6241, Section 8.3.4.1";
+      }
+
+      leaf confirm-timeout {
+        if-feature confirmed-commit;
+        type uint32 {
+          range "1..max";
+        }
+        units "seconds";
+        default "600";   // 10 minutes
+        description
+          "The timeout interval for a confirmed commit.";
+        reference "RFC 6241, Section 8.3.4.1";
+      }
+
+      leaf persist {
+        if-feature confirmed-commit;
+        type string;
+        description
+          "This parameter is used to make a confirmed commit
+           persistent.  A persistent confirmed commit is not aborted
+           if the NETCONF session terminates.  The only way to abort
+           a persistent confirmed commit is to let the timer expire,
+           or to use the <cancel-commit> operation.
+
+           The value of this parameter is a token that must be given
+           in the 'persist-id' parameter of <commit> or
+           <cancel-commit> operations in order to confirm or cancel
+           the persistent confirmed commit.
+
+           The token should be a random string.";
+        reference "RFC 6241, Section 8.3.4.1";
+      }
+
+      leaf persist-id {
+        if-feature confirmed-commit;
+        type string;
+        description
+          "This parameter is given in order to commit a persistent
+           confirmed commit.  The value must be equal to the value
+           given in the 'persist' parameter to the <commit> operation.
+           If it does not match, the operation fails with an
+          'invalid-value' error.";
+        reference "RFC 6241, Section 8.3.4.1";
+      }
+
+    }
+  }
+
+  rpc discard-changes {
+    if-feature candidate;
+
+    description
+      "Revert the candidate configuration to the current
+       running configuration.";
+    reference "RFC 6241, Section 8.3.4.2";
+  }
+
+  rpc cancel-commit {
+    if-feature confirmed-commit;
+    description
+      "This operation is used to cancel an ongoing confirmed commit.
+       If the confirmed commit is persistent, the parameter
+       'persist-id' must be given, and it must match the value of the
+       'persist' parameter.";
+    reference "RFC 6241, Section 8.4.4.1";
+
+    input {
+      leaf persist-id {
+        type string;
+        description
+          "This parameter is given in order to cancel a persistent
+           confirmed commit.  The value must be equal to the value
+           given in the 'persist' parameter to the <commit> operation.
+           If it does not match, the operation fails with an
+          'invalid-value' error.";
+      }
+    }
+  }
+
+  rpc validate {
+    if-feature validate;
+
+    description
+      "Validates the contents of the specified configuration.";
+
+    reference "RFC 6241, Section 8.6.4.1";
+
+    input {
+      container source {
+        description
+          "Particular configuration to validate.";
+
+        choice config-source {
+          mandatory true;
+          description
+            "The configuration source to validate.";
+
+          leaf candidate {
+            if-feature candidate;
+            type empty;
+            description
+              "The candidate configuration is the config source.";
+          }
+          leaf running {
+            type empty;
+            description
+              "The running configuration is the config source.";
+          }
+          leaf startup {
+            if-feature startup;
+            type empty;
+            description
+              "The startup configuration is the config source.";
+          }
+          leaf url {
+            if-feature url;
+            type inet:uri;
+            description
+              "The URL-based configuration is the config source.";
+          }
+          anyxml config {
+            description
+              "Inline Config content: <config> element.  Represents
+               an entire configuration datastore, not
+               a subset of the running datastore.";
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/tests/yanglint/modules/modaction.yang b/tests/yanglint/modules/modaction.yang
new file mode 100644
index 0000000..5a3f92f
--- /dev/null
+++ b/tests/yanglint/modules/modaction.yang
@@ -0,0 +1,26 @@
+module modaction {
+  yang-version 1.1;
+  namespace "urn:yanglint:modaction";
+  prefix ma;
+
+  container con {
+    list ls {
+      key "lfkey";
+      leaf lfkey {
+        type string;
+      }
+      action act {
+        input {
+          leaf lfi {
+            type string;
+          }
+        }
+        output {
+          leaf lfo {
+            type int16;
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modconfig-augment.yang b/tests/yanglint/modules/modconfig-augment.yang
new file mode 100644
index 0000000..d94b366
--- /dev/null
+++ b/tests/yanglint/modules/modconfig-augment.yang
@@ -0,0 +1,15 @@
+module modconfig-augment {
+  yang-version 1.1;
+  namespace "urn:yanglint:modconfig-augment";
+  prefix "mca";
+
+  import modconfig {
+    prefix mc;
+  }
+
+  augment "/mc:mcc" {
+    leaf alf {
+      type string;
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modconfig.yang b/tests/yanglint/modules/modconfig.yang
new file mode 100644
index 0000000..1d12ca6
--- /dev/null
+++ b/tests/yanglint/modules/modconfig.yang
@@ -0,0 +1,17 @@
+module modconfig {
+  namespace "urn:yanglint:modconfig";
+  prefix mc;
+
+  container mcc {
+    leaf lft {
+      type string;
+      config true;
+      mandatory true;
+    }
+    leaf lff {
+      type string;
+      config false;
+      mandatory true;
+    }
+  }
+}
diff --git a/tests/yanglint/modules/moddatanodes.yang b/tests/yanglint/modules/moddatanodes.yang
new file mode 100644
index 0000000..ae4ab20
--- /dev/null
+++ b/tests/yanglint/modules/moddatanodes.yang
@@ -0,0 +1,31 @@
+module moddatanodes {
+  yang-version 1.1;
+  namespace "urn:yanglint:moddatanodes";
+  prefix mdn;
+
+  container dnc {
+    leaf lf {
+      type string;
+    }
+    leaf-list lfl {
+      type string;
+    }
+    leaf mis {
+      type string;
+    }
+    container con {
+      list lt {
+        key "kalf kblf";
+        leaf kalf {
+          type string;
+        }
+        leaf kblf {
+          type string;
+        }
+        leaf vlf {
+          type string;
+        }
+      }
+    }
+  }
+}
diff --git a/tests/yanglint/modules/moddefault.yang b/tests/yanglint/modules/moddefault.yang
new file mode 100644
index 0000000..26570c3
--- /dev/null
+++ b/tests/yanglint/modules/moddefault.yang
@@ -0,0 +1,19 @@
+module moddefault {
+  namespace "urn:yanglint:moddefault";
+  prefix md;
+
+  container mdc {
+    leaf lf {
+      type uint16;
+    }
+    leaf di {
+      type int16;
+      default "5";
+    }
+    leaf ds {
+      type string;
+      default "str";
+    }
+  }
+
+}
diff --git a/tests/yanglint/modules/modextleafref.yang b/tests/yanglint/modules/modextleafref.yang
new file mode 100644
index 0000000..d45ec71
--- /dev/null
+++ b/tests/yanglint/modules/modextleafref.yang
@@ -0,0 +1,24 @@
+module modextleafref {
+  namespace "urn:yanglint:modextleafref";
+  prefix mel;
+
+  list ls {
+    key k;
+    leaf k {
+      type string;
+    }
+    leaf lf {
+      type uint8;
+    }
+  }
+  leaf lfr {
+    type leafref {
+      path "../ls/k";
+    }
+  }
+  leaf lfrderef {
+    type leafref {
+      path "deref(../lfr)/../lf";
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modfeature.yang b/tests/yanglint/modules/modfeature.yang
new file mode 100644
index 0000000..f59d4c8
--- /dev/null
+++ b/tests/yanglint/modules/modfeature.yang
@@ -0,0 +1,7 @@
+module modfeature {
+  namespace "urn:yanglint:modfeature";
+  prefix l;
+
+  feature ftr1;
+  feature ftr2;
+}
diff --git a/tests/yanglint/modules/modimp-cwd.yang b/tests/yanglint/modules/modimp-cwd.yang
new file mode 100644
index 0000000..3249462
--- /dev/null
+++ b/tests/yanglint/modules/modimp-cwd.yang
@@ -0,0 +1,8 @@
+module modimp-cwd {
+  namespace "urn:yanglint:modimp-cwd";
+  prefix ic;
+
+  import modcwd {
+    prefix mc;
+  }
+}
diff --git a/tests/yanglint/modules/modimp-path.yang b/tests/yanglint/modules/modimp-path.yang
new file mode 100644
index 0000000..d9dbb9b
--- /dev/null
+++ b/tests/yanglint/modules/modimp-path.yang
@@ -0,0 +1,8 @@
+module modimp-path {
+  namespace "urn:yanglint:modimp-path";
+  prefix ip;
+
+  import modpath {
+    prefix mp;
+  }
+}
diff --git a/tests/yanglint/modules/modimp-type.yang b/tests/yanglint/modules/modimp-type.yang
new file mode 100644
index 0000000..ec21d31
--- /dev/null
+++ b/tests/yanglint/modules/modimp-type.yang
@@ -0,0 +1,12 @@
+module modimp-type {
+  namespace "urn:yanglint:modimp-type";
+  prefix mit;
+
+  import modtypedef {
+    prefix mtd;
+  }
+
+  leaf lf {
+    type mtd:mui8;
+  }
+}
diff --git a/tests/yanglint/modules/modinclude.yang b/tests/yanglint/modules/modinclude.yang
new file mode 100644
index 0000000..849d43f
--- /dev/null
+++ b/tests/yanglint/modules/modinclude.yang
@@ -0,0 +1,9 @@
+module modinclude {
+  yang-version 1.1;
+  namespace "urn:yanglint:modinclude";
+  prefix mi;
+
+  include "modsub";
+
+  container mic;
+}
diff --git a/tests/yanglint/modules/modleaf.yang b/tests/yanglint/modules/modleaf.yang
new file mode 100644
index 0000000..48ce786
--- /dev/null
+++ b/tests/yanglint/modules/modleaf.yang
@@ -0,0 +1,8 @@
+module modleaf {
+  namespace "urn:yanglint:modleaf";
+  prefix l;
+
+  leaf lfl {
+    type uint16;
+  }
+}
diff --git a/tests/yanglint/modules/modleafref.yang b/tests/yanglint/modules/modleafref.yang
new file mode 100644
index 0000000..f86fb3f
--- /dev/null
+++ b/tests/yanglint/modules/modleafref.yang
@@ -0,0 +1,14 @@
+module modleafref {
+  namespace "urn:yanglint:modleafref";
+  prefix m;
+
+  import modleaf {
+    prefix ml;
+  }
+
+  leaf lfr {
+    type leafref {
+      path "/ml:lfl";
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modmandatory.yang b/tests/yanglint/modules/modmandatory.yang
new file mode 100644
index 0000000..4d48540
--- /dev/null
+++ b/tests/yanglint/modules/modmandatory.yang
@@ -0,0 +1,14 @@
+module modmandatory {
+  namespace "urn:yanglint:modmandatory";
+  prefix mm;
+
+  container mmc {
+    leaf lft {
+      type int16;
+      mandatory true;
+    }
+    leaf lff {
+      type int16;
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modmerge.yang b/tests/yanglint/modules/modmerge.yang
new file mode 100644
index 0000000..60fd75c
--- /dev/null
+++ b/tests/yanglint/modules/modmerge.yang
@@ -0,0 +1,21 @@
+module modmerge {
+  namespace "urn:yanglint:modmerge";
+  prefix mm;
+
+  container mmc {
+    leaf en {
+      type enumeration {
+        enum zero;
+        enum one;
+      }
+    }
+    leaf lm {
+      type int16;
+      must "../en != 'zero'";
+    }
+    leaf lf {
+      type string;
+    }
+  }
+
+}
diff --git a/tests/yanglint/modules/modmust.yang b/tests/yanglint/modules/modmust.yang
new file mode 100644
index 0000000..99971bd
--- /dev/null
+++ b/tests/yanglint/modules/modmust.yang
@@ -0,0 +1,13 @@
+module modmust {
+  namespace "urn:yanglint:modmust";
+  prefix m;
+
+  import modleaf {
+    prefix ml;
+  }
+
+  leaf lfm {
+    type string;
+    must "/ml:lfl > 0";
+  }
+}
diff --git a/tests/yanglint/modules/modnotif.yang b/tests/yanglint/modules/modnotif.yang
new file mode 100644
index 0000000..a2155a0
--- /dev/null
+++ b/tests/yanglint/modules/modnotif.yang
@@ -0,0 +1,19 @@
+module modnotif {
+  yang-version 1.1;
+  namespace "urn:yanglint:modnotif";
+  prefix mn;
+
+  container con {
+    notification nfn {
+      leaf lf {
+        type string;
+      }
+    }
+  }
+
+  notification nfg {
+    leaf lf {
+      type string;
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modoper-leafref.yang b/tests/yanglint/modules/modoper-leafref.yang
new file mode 100644
index 0000000..36a1124
--- /dev/null
+++ b/tests/yanglint/modules/modoper-leafref.yang
@@ -0,0 +1,68 @@
+module modoper-leafref {
+  yang-version 1.1;
+  namespace "urn:yanglint:modoper-leafref";
+  prefix mol;
+
+  import modconfig {
+    prefix mc;
+  }
+
+  container cond {
+    list list {
+      key "klf";
+      leaf klf {
+        type string;
+      }
+      action act {
+        input {
+          leaf lfi {
+            type leafref {
+              path "/mc:mcc/mc:lft";
+            }
+          }
+        }
+        output {
+          leaf lfo {
+            type leafref {
+              path "/mc:mcc/mc:lft";
+            }
+          }
+        }
+      }
+      notification notif {
+        leaf lfn {
+          type leafref {
+            path "/mc:mcc/mc:lft";
+          }
+        }
+      }
+    }
+  }
+
+  rpc rpcg {
+    input {
+      leaf lfi {
+        type leafref {
+          path "/mc:mcc/mc:lft";
+        }
+      }
+    }
+    output {
+      container cono {
+        leaf lfo {
+          type leafref {
+            path "/mc:mcc/mc:lft";
+          }
+        }
+      }
+    }
+  }
+
+  notification notifg {
+    leaf lfr {
+      type leafref {
+        path "/mc:mcc/mc:lft";
+      }
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modpath.yang b/tests/yanglint/modules/modpath.yang
new file mode 100644
index 0000000..da099a2
--- /dev/null
+++ b/tests/yanglint/modules/modpath.yang
@@ -0,0 +1,4 @@
+module modpath {
+  namespace "urn:yanglint:modpath";
+  prefix mp;
+}
diff --git a/tests/yanglint/modules/modrpc.yang b/tests/yanglint/modules/modrpc.yang
new file mode 100644
index 0000000..dc0cced
--- /dev/null
+++ b/tests/yanglint/modules/modrpc.yang
@@ -0,0 +1,19 @@
+module modrpc {
+  namespace "urn:yanglint:modrpc";
+  prefix mr;
+
+  rpc rpc {
+    input {
+      leaf lfi {
+        type string;
+      }
+    }
+    output {
+      container con {
+        leaf lfo {
+          type int16;
+        }
+      }
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modsm-augment.yang b/tests/yanglint/modules/modsm-augment.yang
new file mode 100644
index 0000000..5d16fbd
--- /dev/null
+++ b/tests/yanglint/modules/modsm-augment.yang
@@ -0,0 +1,15 @@
+module modsm-augment {
+  yang-version 1.1;
+  namespace "urn:yanglint:modsm-augment";
+  prefix "msa";
+
+  import modsm {
+    prefix msm;
+  }
+
+  augment "/msm:root" {
+    leaf alf {
+      type string;
+    }
+  }
+}
diff --git a/tests/yanglint/modules/modsm.yang b/tests/yanglint/modules/modsm.yang
new file mode 100644
index 0000000..dfe8830
--- /dev/null
+++ b/tests/yanglint/modules/modsm.yang
@@ -0,0 +1,13 @@
+module modsm {
+  yang-version 1.1;
+  namespace "urn:yanglint:modsm";
+  prefix "msm";
+
+  import ietf-yang-schema-mount {
+    prefix sm;
+  }
+
+  container root {
+    sm:mount-point "root";
+  }
+}
diff --git a/tests/yanglint/modules/modsub.yang b/tests/yanglint/modules/modsub.yang
new file mode 100644
index 0000000..79d9286
--- /dev/null
+++ b/tests/yanglint/modules/modsub.yang
@@ -0,0 +1,8 @@
+submodule modsub {
+  yang-version 1.1;
+  belongs-to modinclude {
+    prefix mi;
+  }
+
+  container msc;
+}
diff --git a/tests/yanglint/modules/modtypedef.yang b/tests/yanglint/modules/modtypedef.yang
new file mode 100644
index 0000000..ea09c95
--- /dev/null
+++ b/tests/yanglint/modules/modtypedef.yang
@@ -0,0 +1,8 @@
+module modtypedef {
+  namespace "urn:yanglint:typedef";
+  prefix mt;
+
+  typedef mui8 {
+    type uint8;
+  }
+}
diff --git a/tests/yanglint/non-interactive/all.tcl b/tests/yanglint/non-interactive/all.tcl
new file mode 100644
index 0000000..998c03a
--- /dev/null
+++ b/tests/yanglint/non-interactive/all.tcl
@@ -0,0 +1,15 @@
+package require tcltest
+
+# Hook to determine if any of the tests failed.
+# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0.
+proc tcltest::cleanupTestsHook {} {
+    variable numTests
+    set ::exitCode [expr {$numTests(Failed) > 0}]
+}
+
+if {[info exists ::env(TESTS_DIR)]} {
+    tcltest::configure -testdir "$env(TESTS_DIR)/non-interactive"
+}
+
+tcltest::runAllTests
+exit $exitCode
diff --git a/tests/yanglint/non-interactive/data_default.test b/tests/yanglint/non-interactive/data_default.test
new file mode 100644
index 0000000..be19d72
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_default.test
@@ -0,0 +1,31 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mods "$::env(YANG_MODULES_DIR)/ietf-netconf-with-defaults@2011-06-01.yang $::env(YANG_MODULES_DIR)/moddefault.yang"
+set data "$::env(TESTS_DIR)/data/moddefault.xml"
+
+test data_default_not_set {Print data without --default parameter} {
+    ly_cmd "-f xml $mods $data" "</lf>.*</di>\n</mdc>"
+    ly_cmd "-f json $mods $data" "lf\".*di\"\[^\"]*"
+} {}
+
+test data_default_all {data --default all} {
+    ly_cmd "-d all -f xml $mods $data" "</lf>.*</di>.*</ds>\n</mdc>"
+    ly_cmd "-d all -f json $mods $data" "lf\".*di\".*ds\"\[^\"]*"
+} {}
+
+test data_default_all_tagged {data --default all-tagged} {
+    ly_cmd "-d all-tagged -f xml $mods $data" "</lf>.*<di.*default.*</di>.*<ds.*default.*</ds>\n</mdc>"
+    ly_cmd "-d all-tagged -f json $mods $data" "lf\".*di\".*ds\".*@ds\".*default\"\[^\"]*"
+} {}
+
+test data_default_trim {data --default trim} {
+    ly_cmd "-d trim -f xml $mods $data" "</lf>\n</mdc>"
+    ly_cmd "-d trim -f json $mods $data" "lf\"\[^\"]*"
+} {}
+
+test data_default_implicit_tagged {data --default implicit-tagged} {
+    ly_cmd "-d implicit-tagged -f xml $mods $data" "</lf>.*<di>5</di>.*<ds.*default.*</ds>\n</mdc>"
+    ly_cmd "-d implicit-tagged -f json $mods $data" "lf\".*di\"\[^@]*ds\".*default\"\[^\"]*"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_in_format.test b/tests/yanglint/non-interactive/data_in_format.test
new file mode 100644
index 0000000..f1336dd
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_in_format.test
@@ -0,0 +1,18 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_in_format_xml {--in-format xml} {
+    ly_cmd "-I xml $mdir/modleaf.yang $ddir/modleaf.dxml"
+    ly_cmd_err "-I json $mdir/modleaf.yang $ddir/modleaf.dxml" "Failed to parse"
+    ly_cmd_err "-I lyb $mdir/modleaf.yang $ddir/modleaf.dxml" "Failed to parse"
+} {}
+
+test data_in_format_json {--in-format json} {
+    ly_cmd "-I json $mdir/modleaf.yang $ddir/modleaf.djson"
+    ly_cmd_err "-I xml $mdir/modleaf.yang $ddir/modleaf.djson" "Failed to parse"
+    ly_cmd_err "-I lyb $mdir/modleaf.yang $ddir/modleaf.djson" "Failed to parse"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_merge.test b/tests/yanglint/non-interactive/data_merge.test
new file mode 100644
index 0000000..4ecfcee
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_merge.test
@@ -0,0 +1,28 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_merge_basic {Data is merged and the node is added} {
+    ly_cmd "-m -f xml $mdir/modmerge.yang $ddir/modmerge.xml $ddir/modmerge3.xml" "<en>.*<lm>.*<lf>"
+} {}
+
+test data_merge_validation_failed {Data is merged but validation failed.} {
+    ly_cmd "$mdir/modmerge.yang $ddir/modmerge.xml"
+    ly_cmd "$mdir/modmerge.yang $ddir/modmerge2.xml"
+    ly_cmd "-m $mdir/modmerge.yang $ddir/modmerge2.xml $ddir/modmerge.xml"
+    ly_cmd_err "-m $mdir/modmerge.yang $ddir/modmerge.xml $ddir/modmerge2.xml" "Merged data are not valid"
+} {}
+
+test data_merge_dataconfig {The merge option has effect only for 'data' and 'config' TYPEs} {
+    set wrn1 "option has effect only for"
+    ly_cmd_wrn "-m -t rpc $mdir/modrpc.yang $ddir/modrpc.xml $ddir/modrpc.xml" $wrn1
+    ly_cmd_wrn "-m -t notif $mdir/modnotif.yang $ddir/modnotif2.xml $ddir/modnotif2.xml" $wrn1
+    ly_cmd_wrn "-m -t get $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig.xml" $wrn1
+    ly_cmd_wrn "-m -t getconfig $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+    ly_cmd_wrn "-m -t edit $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1
+    ly_cmd "-m -t config $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml"
+    ly_cmd "-m -t data $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig.xml"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_not_strict.test b/tests/yanglint/non-interactive/data_not_strict.test
new file mode 100644
index 0000000..b91eed8
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_not_strict.test
@@ -0,0 +1,20 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_no_strict_basic {} {
+    ly_cmd_err "$ddir/modmandatory.xml $mdir/modleaf.yang" "No module with namespace \"urn:yanglint:modmandatory\" in the context."
+    ly_cmd "-n $ddir/modmandatory.xml $mdir/modleaf.yang"
+} {}
+
+test data_no_strict_invalid_data {validation with --no-strict but data are invalid} {
+    set errmsg "Mandatory node \"lft\" instance does not exist."
+    ly_cmd_err "-n $ddir/modmandatory_invalid.xml $mdir/modmandatory.yang" $errmsg
+} {}
+
+test data_no_strict_ignore_invalid_data {--no-strict ignore invalid data if no schema is provided} {
+    ly_cmd "-f xml -n $ddir/modmandatory_invalid.xml $ddir/modleaf.xml $mdir/modleaf.yang" "modleaf.*</lfl>$"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_operational.test b/tests/yanglint/non-interactive/data_operational.test
new file mode 100644
index 0000000..82e861e
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_operational.test
@@ -0,0 +1,62 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types"
+
+test data_operational_twice {it is not allowed to specify more than one --operational parameter} {
+    ly_cmd_err "-t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times"
+} {}
+
+test data_operational_no_type {--operational should be with parameter --type} {
+    ly_cmd_err "-O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif.xml" $err1
+} {}
+
+test data_operational_missing {--operational is omitted and the datastore contents is in the data file} {
+    ly_cmd_err "$mdir/modoper-leafref.yang $ddir/modoper_leafref_notif_err.xml" "Failed to parse input data file"
+} {}
+
+test data_operational_wrong_type {data are not defined as an operation} {
+    ly_cmd_wrn "-t data -O $ddir/modconfig.xml $mdir/modleaf.yang $ddir/modleaf.xml" $err1
+} {}
+
+test data_operational_datastore_with_unknown_data {unknown data are ignored} {
+    ly_cmd "-t rpc -O $ddir/modmandatory_invalid.xml $mdir/modrpc.yang $ddir/modrpc.xml"
+} {}
+
+test data_operational_empty_datastore {datastore is considered empty because it contains unknown data} {
+    ly_cmd "-t rpc -O $ddir/modmandatory_invalid.xml $mdir/modrpc.yang $ddir/modrpc.xml"
+    set msg "parent \"/modnotif:con\" not found in the operational data"
+    ly_cmd_err "-t notif -O $ddir/modmandatory_invalid.xml $mdir/modnotif.yang $ddir/modnotif.xml" $msg
+} {}
+
+test data_operational_notif_leafref {--operational data is referenced from notification-leafref} {
+    ly_cmd "-t notif -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif.xml"
+} {}
+
+test data_operational_nested_notif_leafref {--operational data is referenced from nested-notification-leafref} {
+    ly_cmd "-t notif -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif2.xml"
+} {}
+
+test data_operational_nested_notif_parent_missing {--operational data are invalid due to missing parent node} {
+    set msg "klf='key_val']\" not found in the operational data"
+    ly_cmd_err "-t notif -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif2.xml" $msg
+} {}
+
+test data_operational_action_leafref {--operational data is referenced from action-leafref} {
+    ly_cmd "-t rpc -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_action.xml"
+} {}
+
+test data_operational_action_reply_leafref {--operational data is referenced from action-leafref output} {
+    ly_cmd "-t reply -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_action_reply.xml"
+} {}
+
+test data_operational_rpc_leafref {--operational data is referenced from rpc-leafref} {
+    ly_cmd "-t rpc -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_rpc.xml"
+} {}
+
+test data_operational_rpc_reply_leafref {--operational data is referenced from rpc-leafref output} {
+    ly_cmd "-t reply -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_rpc_reply.xml"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_present.test b/tests/yanglint/non-interactive/data_present.test
new file mode 100644
index 0000000..81aac14
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_present.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+
+test data_present_via_mandatory {validation of mandatory-stmt will pass only with the --present} {
+    set mods "$mdir/modleaf.yang $mdir/modmandatory.yang"
+    ly_cmd_err "$ddir/modleaf.xml $mods" "Mandatory node \"lft\" instance does not exist."
+    ly_cmd "-e $ddir/modleaf.xml $mods"
+} {}
+
+test data_present_merge {validation with --present and --merge} {
+    set mods "$mdir/modleaf.yang $mdir/modmandatory.yang $mdir/moddefault.yang"
+    set data "$ddir/modleaf.xml $ddir/moddefault.xml"
+    ly_cmd_err "-m $data $mods" "Mandatory node \"lft\" instance does not exist."
+    ly_cmd "-m -e $data $mods"
+} {}
+
+test data_present_merge_invalid {using --present and --merge but data are invalid} {
+    set mods "$mdir/modleaf.yang $mdir/modmandatory.yang"
+    set data "$ddir/modleaf.xml $ddir/modmandatory_invalid.xml"
+    ly_cmd_err "-e -m  $data $mods" "Mandatory node \"lft\" instance does not exist."
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_type.test b/tests/yanglint/non-interactive/data_type.test
new file mode 100644
index 0000000..e3691d7
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_type.test
@@ -0,0 +1,107 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+set modnc "$mdir/ietf-netconf@2011-06-01.yang"
+
+test data_type_data {data --type data} {
+    ly_cmd "-t data $mdir/modconfig.yang $ddir/modconfig.xml"
+} {}
+
+test data_type_config {data --type config} {
+    ly_cmd_err "-t config $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+    ly_cmd "-t config $mdir/modconfig.yang $ddir/modconfig2.xml"
+} {}
+
+test data_type_get {data --type get} {
+    ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value"
+    ly_cmd "-t get $mdir/modleafref.yang $ddir/modleafref2.xml"
+} {}
+
+test data_type_getconfig_no_state {No state node for data --type getconfig} {
+    ly_cmd_err "-t getconfig $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+    ly_cmd "-t getconfig $mdir/modconfig.yang $ddir/modconfig2.xml"
+} {}
+
+test data_type_getconfig_parse_only {No validation performed for data --type getconfig} {
+    ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value"
+    ly_cmd "-t getconfig $mdir/modleafref.yang $ddir/modleafref2.xml"
+} {}
+
+test data_type_edit_no_state {No state node for data --type edit} {
+    ly_cmd_err "-t edit $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\""
+    ly_cmd "-t edit $mdir/modconfig.yang $ddir/modconfig2.xml"
+} {}
+
+test data_type_edit_parse_only {No validation performed for data --type edit} {
+    ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value"
+    ly_cmd "-t edit $mdir/modleafref.yang $ddir/modleafref2.xml"
+} {}
+
+test data_type_rpc {Validation of rpc-statement by data --type rpc} {
+    ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "-t rpc $mdir/modrpc.yang $ddir/modrpc.xml"
+} {}
+
+test data_type_rpc_nc {Validation of rpc-statement by data --type nc-rpc} {
+    ly_cmd_err "-t nc-rpc $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+    ly_cmd "-t nc-rpc $modnc $mdir/modrpc.yang $ddir/modrpc_nc.xml"
+} {}
+
+test data_type_rpc_reply {Validation of rpc-reply by data --type reply} {
+    ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd_wrn "-t reply -R $ddir/modrpc.xml $mdir/modrpc.yang $ddir/modrpc_reply.xml" "needed only for NETCONF"
+    ly_cmd "-t reply $mdir/modrpc.yang $ddir/modrpc_reply.xml"
+} {}
+
+test data_type_rpc_reply_nc {Validation of rpc-reply by data --type nc-reply} {
+    set err1 "Missing NETCONF <rpc-reply> envelope"
+    ly_cmd_err "-t nc-reply -R $ddir/modrpc_nc.xml $mdir/modrpc.yang $mdir/modleaf.yang $ddir/modleaf.xml" $err1
+    ly_cmd_err "-t nc-reply $mdir/modrpc.yang $ddir/modrpc_reply_nc.xml" "Missing source RPC"
+    ly_cmd "-t nc-reply -R $ddir/modrpc_nc.xml $mdir/modrpc.yang $ddir/modrpc_reply_nc.xml"
+} {}
+
+test data_type_rpc_action {Validation of action-statement by data --type rpc} {
+    ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "-t rpc -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction.xml"
+} {}
+
+test data_type_rpc_action_nc {Validation of action-statement by data --type nc-rpc} {
+    ly_cmd_err "-t nc-rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope"
+    ly_cmd "-t nc-rpc -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction_nc.xml"
+} {}
+
+test data_type_rpc_action_reply {Validation of action-reply by data --type reply} {
+    ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "-t reply -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction_reply.xml"
+} {}
+
+test data_type_rpc_action_reply_nc {Validation of action-reply by data --type nc-reply} {
+    set err1 "Missing NETCONF <rpc-reply> envelope"
+    set err2 "operational parameter needed"
+    ly_cmd_err "-t nc-reply -R $ddir/modaction_nc.xml $mdir/modaction.yang $mdir/modleaf.yang $ddir/modleaf.xml" $err1
+    ly_cmd_err "-t nc-reply $mdir/modaction.yang $ddir/modaction_reply_nc.xml" "Missing source RPC"
+    ly_cmd_err "-t nc-reply -R $ddir/modaction_nc.xml $mdir/modaction.yang $ddir/modaction_reply_nc.xml" $err2
+    ly_cmd "-t nc-reply -O $ddir/modaction_ds.xml -R $ddir/modaction_nc.xml $mdir/modaction.yang $ddir/modaction_reply_nc.xml"
+} {}
+
+test data_type_notif {Validation of notification-statement by data --type notif} {
+    ly_cmd_err "-t notif $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node."
+    ly_cmd "-t notif $mdir/modnotif.yang $ddir/modnotif2.xml"
+} {}
+
+test data_type_notif_nc {Validation of notification-statement by data --type nc-notif} {
+    ly_cmd_err "-t nc-notif $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+    ly_cmd "-t nc-notif $modnc $mdir/modnotif.yang $ddir/modnotif2_nc.xml"
+} {}
+
+test data_type_notif_nested {Validation of nested-notification-statement by data --type notif} {
+    ly_cmd "-t notif -O $ddir/modnotif_ds.xml $mdir/modnotif.yang $ddir/modnotif.xml"
+} {}
+
+test data_type_notif_nested_nc {Validation of nested-notification-statement by data --type nc-notif} {
+    ly_cmd_err "-t nc-notif $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <notification> envelope"
+    ly_cmd "-t nc-notif -O $ddir/modnotif_ds.xml $modnc $mdir/modnotif.yang $ddir/modnotif_nc.xml"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/data_xpath.test b/tests/yanglint/non-interactive/data_xpath.test
new file mode 100644
index 0000000..1d96106
--- /dev/null
+++ b/tests/yanglint/non-interactive/data_xpath.test
@@ -0,0 +1,42 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mod "$::env(YANG_MODULES_DIR)/moddatanodes.yang"
+set data "$::env(TESTS_DIR)/data/moddatanodes.xml"
+
+test data_xpath_empty {--data-path to missing node} {
+    ly_cmd "-E /moddatanodes:dnc/mis $mod $data" "Empty"
+} {}
+
+test data_xpath_leaf {--xpath to leaf node} {
+    ly_cmd "-E /moddatanodes:dnc/lf $mod $data" "leaf \"lf\" \\(value: \"x\"\\)"
+} {}
+
+test data_xpath_leaflist {--xpath to leaf-list node} {
+    set r1 "leaf-list \"lfl\" \\(value: \"1\"\\)"
+    set r2 "leaf-list \"lfl\" \\(value: \"2\"\\)"
+    ly_cmd "-E /moddatanodes:dnc/lfl $mod $data" "$r1\n  $r2"
+} {}
+
+test data_xpath_list {--xpath to list} {
+    set r1 "list \"lt\" \\(\"kalf\": \"ka1\"; \"kblf\": \"kb1\";\\)"
+    set r2 "list \"lt\" \\(\"kalf\": \"ka2\"; \"kblf\": \"kb2\";\\)"
+    ly_cmd "-E /moddatanodes:dnc/con/lt $mod $data" "$r1\n  $r2"
+} {}
+
+test data_xpath_container {--xpath to container} {
+    ly_cmd "-E /moddatanodes:dnc/con $mod $data" "container \"con\""
+} {}
+
+test data_xpath_wrong_path {--xpath to a non-existent node} {
+    ly_cmd_err "-E /moddatanodes:dnc/wrng $mod $data" "xpath failed"
+} {}
+
+test data_xpath_err_format {--xpath cannot be combined with --format} {
+    ly_cmd_err "-f xml -E /moddatanodes:dnc/lf $mod $data" "option cannot be combined"
+} {}
+
+test data_xpath_err_default {--xpath cannot be combined with --default} {
+    ly_cmd_err "-d all -E /moddatanodes:dnc/lf $mod $data" "option cannot be combined"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/debug.test b/tests/yanglint/non-interactive/debug.test
new file mode 100644
index 0000000..4543acb
--- /dev/null
+++ b/tests/yanglint/non-interactive/debug.test
@@ -0,0 +1,25 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test debug_dict {Check debug message DICT} {
+-constraints {[ly_opt_exists "-G"]} -body {
+    ly_cmd_wrn "-V -V -G dict $mdir/modleaf.yang" "DICT"
+}}
+
+test debug_xpath {Check debug message XPATH} {
+-constraints {[ly_opt_exists "-G"]} -body {
+    ly_cmd_wrn "-V -V -G xpath $mdir/modmust.yang" "XPATH"
+}}
+
+test debug_dep_sets {Check debug message DEPSETS} {
+-constraints {[ly_opt_exists "-G"]} -body {
+    ly_cmd_wrn "-V -V -G dep-sets $mdir/modleaf.yang" "DEPSETS"
+}}
+
+test debug_depsets_xpath {Check debug message DEPSETS and XPATH} {
+-constraints {[ly_opt_exists "-G"]} -body {
+    ly_cmd_wrn "-V -V -G dep-sets,xpath $mdir/modmust.yang" "DEPSETS.*XPATH"
+}}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/disabled_searchdir.test b/tests/yanglint/non-interactive/disabled_searchdir.test
new file mode 100644
index 0000000..49fe13e
--- /dev/null
+++ b/tests/yanglint/non-interactive/disabled_searchdir.test
@@ -0,0 +1,18 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $env(YANG_MODULES_DIR)
+
+# Test should be skipped if called by ctest.
+test disable_searchdir_once {Unsuccessfully imports module due to disabled cwd searching} {
+-constraints {!ctest} -body {
+    ly_cmd "$mdir/modimp-cwd.yang"
+    ly_cmd_err "-D $mdir/modimp-cwd.yang" "not found in local searchdirs"
+}}
+
+test disable_searchdir_twice {Unsuccessfully imports module due to -D -D} {
+    ly_cmd "$mdir/ietf-ip.yang"
+    ly_cmd_err "-D -D $mdir/ietf-ip.yang" "Loading \"ietf-interfaces\" module failed."
+} {}
+
+cleanupTests
+
diff --git a/tests/yanglint/non-interactive/ext_data.test b/tests/yanglint/non-interactive/ext_data.test
new file mode 100644
index 0000000..d4e3c44
--- /dev/null
+++ b/tests/yanglint/non-interactive/ext_data.test
@@ -0,0 +1,29 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+
+test ext_data_schema_mount_tree {Print tree output of a model with Schema Mount} {
+    # mounting node lfl from modleaf.yang into modsm.yang
+    set out1 "--mp root.*--rw lfl/"
+    ly_cmd "-f tree -p $mdir -y -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang" $out1
+} {}
+
+test ext_data_schema_mount_tree_yanglibfile {Print tree output of a model with Schema Mount and --yang-library-file} {
+    # yang-library-file context contains an augment node 'alf' for modsm
+    set out1 "--mp root.*--rw lfl/.*--rw msa:alf?"
+    ly_cmd "-f tree -p $mdir -Y $ddir/modsm_ctx_main.xml -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang" $out1
+} {}
+
+test ext_data_schema_mount_xml {Validating and printing mounted data} {
+    ly_cmd "-f xml -t config -p $mdir -y -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang $ddir/modsm.xml" "</lfl>"
+} {}
+
+test ext_data_schema_mount_xml_yanglibfile {Validating and printing mounted data with --yang-library-file} {
+    set yanglibfile "$ddir/modsm_ctx_main.xml"
+    set extdata "$ddir/modsm_ctx_ext.xml"
+    set out1 "</lfl>.*</alf>"
+    ly_cmd "-f xml -t config -p $mdir -Y $yanglibfile -x $extdata $mdir/modsm.yang $ddir/modsm2.xml" $out1
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/extended_leafref.test b/tests/yanglint/non-interactive/extended_leafref.test
new file mode 100644
index 0000000..5e1a90e
--- /dev/null
+++ b/tests/yanglint/non-interactive/extended_leafref.test
@@ -0,0 +1,13 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test extended_leafref_enabled {Valid module with --extended-leafref option} {
+    ly_cmd "-X $mdir/modextleafref.yang"
+} {}
+
+test extended_leafref_disabled {Expected error if --extended-leafref is not set} {
+    ly_cmd_err "$mdir/modextleafref.yang" "Unexpected XPath token \"FunctionName\""
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/format.test b/tests/yanglint/non-interactive/format.test
new file mode 100644
index 0000000..8df5544
--- /dev/null
+++ b/tests/yanglint/non-interactive/format.test
@@ -0,0 +1,72 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+set ddir $::env(TESTS_DIR)/data
+set ipv6_path "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address"
+
+test format_yang {} {
+    ly_cmd "-f yang $mdir/modleaf.yang" "leaf lfl"
+} {}
+
+test format_yang_submodule {Print submodule in yang format} {
+    ly_cmd "-s modsub -f yang $mdir/modinclude.yang" "submodule modsub"
+} {}
+
+test format_yin {} {
+    ly_cmd "-f yin $mdir/modleaf.yang" "<leaf name=\"lfl\">"
+} {}
+
+test format_yin_submodule {Print submodule in yin format} {
+    ly_cmd "-s modsub -f yin $mdir/modinclude.yang" "<submodule name=\"modsub\""
+} {}
+
+test format_info {} {
+    ly_cmd "-f info $mdir/modleaf.yang" "status current"
+} {}
+
+test format_tree {} {
+    ly_cmd "-f tree $mdir/modleaf.yang" "\\+--rw lfl"
+} {}
+
+test format_data_xml {Print data in xml format} {
+    ly_cmd "-f xml $mdir/modleaf.yang $ddir/modleaf.xml" "<lfl xmlns=\"urn:yanglint:modleaf\">7</lfl>"
+} {}
+
+test format_data_json {Print data in json format} {
+    ly_cmd "-f json $mdir/modleaf.yang $ddir/modleaf.xml" "{\n  \"modleaf:lfl\": 7\n}"
+} {}
+
+test format_data_lyb_err {Printing in LYB format: expect error due to missing parameter} {
+    ly_cmd_err "-f lyb $mdir/modleaf.yang $ddir/modleaf.xml" "The LYB format requires the -o"
+} {}
+
+test format_tree_submodule {Print submodule in tree format} {
+    ly_cmd "-s modsub -f tree $mdir/modinclude.yang" "submodule: modsub"
+} {}
+
+test format_tree_path {Print subtree in tree format} {
+    ly_cmd "-f tree -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address.*\\+--rw prefix-length"
+} {}
+
+test format_tree_path_single_node {Print node in tree format} {
+    ly_cmd "-f tree -q -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address\\* \\\[ip\\\]$"
+} {}
+
+test format_tree_path_single_node_line_length {Print node in the tree format and limit row size} {
+    ly_cmd "-f tree -L 20 -q -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address\\*\n *\\\[ip\\\]$"
+} {}
+
+test format_feature_param_one_module {Show features for one module} {
+    ly_cmd "-f feature-param $mdir/ietf-ip.yang" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" -ex
+} {}
+
+test format_feature_param_more_modules {Show a mix of modules with and without features} {
+
+    set features " -F modfeature:ftr1,ftr2\
+-F modleaf:\
+-F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
+
+    ly_cmd "-f feature-param $mdir/modfeature.yang $mdir/modleaf.yang $mdir/ietf-ip.yang" $features -ex
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/list.test b/tests/yanglint/non-interactive/list.test
new file mode 100644
index 0000000..626d9a1
--- /dev/null
+++ b/tests/yanglint/non-interactive/list.test
@@ -0,0 +1,26 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+namespace import uti::regex_xml_elements uti::regex_json_pairs
+
+set modules {ietf-yang-library ietf-inet-types}
+
+test list_basic {} {
+    ly_cmd "-l" "ietf-yang-types"
+} {}
+
+test list_format_xml {list --format xml} {
+    ly_cmd "-y -f xml -l" [regex_xml_elements $modules "name"]
+} {}
+
+test list_format_json {list --format json} {
+    ly_cmd "-y -f json -l" [regex_json_pairs $modules "name"]
+} {}
+
+test list_ietf_yang_library {Error due to missing ietf-yang-library} {
+    ly_cmd_err "-f xml -l" "Module \"ietf-yang-library\" is not implemented."
+} {}
+
+test list_bad_format {Error due to bad format} {
+    ly_cmd_err "-f csv -l" "Unknown output format csv"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/ly.tcl b/tests/yanglint/non-interactive/ly.tcl
new file mode 100644
index 0000000..f6bb2c7
--- /dev/null
+++ b/tests/yanglint/non-interactive/ly.tcl
@@ -0,0 +1,8 @@
+# @brief The main source of functions and variables for testing yanglint in the non-interactive mode.
+
+# For testing yanglint.
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/common.tcl" : "../common.tcl"}]
+# For testing any non-interactive tool.
+source "$::env(TESTS_DIR)/../tool_ni.tcl"
+
+# The script continues by defining variables and functions specific to the non-interactive yanglint tool.
diff --git a/tests/yanglint/non-interactive/make_implemented.test b/tests/yanglint/non-interactive/make_implemented.test
new file mode 100644
index 0000000..40cead9
--- /dev/null
+++ b/tests/yanglint/non-interactive/make_implemented.test
@@ -0,0 +1,17 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $::env(YANG_MODULES_DIR)
+
+test make_impl_no_set {Import while --make-implemented is not set} {
+    ly_cmd "-l $mdir/modleafref.yang" "I modleafref\n.*I modleaf"
+} {}
+
+test make_impl_set_once {--make-implemented} {
+    ly_cmd "-l -i $mdir/modmust.yang" "I modmust\n.*I modleaf"
+} {}
+
+test make_impl_set_twice {-i -i} {
+    ly_cmd "-l -i -i $mdir/modimp-type.yang" "I modimp-type\n.*I modtypedef"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/modcwd.yang b/tests/yanglint/non-interactive/modcwd.yang
new file mode 100644
index 0000000..db33e73
--- /dev/null
+++ b/tests/yanglint/non-interactive/modcwd.yang
@@ -0,0 +1,4 @@
+module modcwd {
+  namespace "urn:yanglint:modcwd";
+  prefix mc;
+}
diff --git a/tests/yanglint/non-interactive/path.test b/tests/yanglint/non-interactive/path.test
new file mode 100644
index 0000000..bf915ff
--- /dev/null
+++ b/tests/yanglint/non-interactive/path.test
@@ -0,0 +1,9 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir $env(YANG_MODULES_DIR)
+
+test path_basic {} {
+    ly_cmd "-p $::env(TESTS_DIR)/data $::env(YANG_MODULES_DIR)/modimp-path.yang"
+} {}
+
+cleanupTests
diff --git a/tests/yanglint/non-interactive/yang_library_file.test b/tests/yanglint/non-interactive/yang_library_file.test
new file mode 100644
index 0000000..bd95978
--- /dev/null
+++ b/tests/yanglint/non-interactive/yang_library_file.test
@@ -0,0 +1,18 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}]
+
+set mdir "$::env(YANG_MODULES_DIR)"
+set ddir "$::env(TESTS_DIR)/data"
+
+test ylf_list {apply --yang-library-file and check result by --list} {
+    ly_cmd "-Y $ddir/modimp_type_ctx.xml -p $mdir -l" "I modimp-type.*i modtypedef"
+} {}
+
+test ylf_make_implemented {apply --yang-library-file and --make-implemented} {
+    ly_cmd "-Y $ddir/modimp_type_ctx.xml -p $mdir -i -i -l" "I modimp-type.*I modtypedef"
+} {}
+
+test ylf_augment_ctx {Setup context by yang-library-file and augment module} {
+    ly_cmd "-Y $ddir/modconfig_ctx.xml -p $mdir -f tree $mdir/modconfig.yang $mdir/modconfig-augment.yang" "mca:alf"
+} {}
+
+cleanupTests
diff --git a/tests/yangre/CMakeLists.txt b/tests/yangre/CMakeLists.txt
new file mode 100644
index 0000000..ce5b39b
--- /dev/null
+++ b/tests/yangre/CMakeLists.txt
@@ -0,0 +1,8 @@
+find_program(PATH_TCLSH NAMES tclsh)
+if(NOT PATH_TCLSH)
+    message(WARNING "'tclsh' not found! The yangre test will not be available.")
+else()
+    add_test(NAME "yangre" COMMAND "tclsh" "${CMAKE_CURRENT_SOURCE_DIR}/all.tcl")
+    set_property(TEST "yangre" APPEND PROPERTY ENVIRONMENT "TESTS_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
+    set_property(TEST "yangre" APPEND PROPERTY ENVIRONMENT "YANGRE=${PROJECT_BINARY_DIR}")
+endif()
diff --git a/tests/yangre/all.tcl b/tests/yangre/all.tcl
new file mode 100644
index 0000000..f00563f
--- /dev/null
+++ b/tests/yangre/all.tcl
@@ -0,0 +1,15 @@
+package require tcltest
+
+# Hook to determine if any of the tests failed.
+# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0.
+proc tcltest::cleanupTestsHook {} {
+    variable numTests
+    set ::exitCode [expr {$numTests(Failed) > 0}]
+}
+
+if {[info exists ::env(TESTS_DIR)]} {
+    tcltest::configure -testdir "$env(TESTS_DIR)"
+}
+
+tcltest::runAllTests
+exit $exitCode
diff --git a/tests/yangre/arg.test b/tests/yangre/arg.test
new file mode 100644
index 0000000..821aad1
--- /dev/null
+++ b/tests/yangre/arg.test
@@ -0,0 +1,19 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+test arg_empty {Missing arguments} {
+    ly_cmd_err "" "missing <string> parameter to process"
+} {}
+
+test arg_wrong {Wrong argument} {
+    ly_cmd_err "-j" "invalid option"
+} {}
+
+test arg_help {Print help} {
+    ly_cmd "-h" "Usage:"
+} {}
+
+test arg_version {Print version} {
+    ly_cmd "-v" "yangre"
+} {}
+
+cleanupTests
diff --git a/tests/yangre/file.test b/tests/yangre/file.test
new file mode 100644
index 0000000..80ea3f6
--- /dev/null
+++ b/tests/yangre/file.test
@@ -0,0 +1,37 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+set fdir "$::env(TESTS_DIR)/files"
+
+test file_empty {file is empty} {
+    ly_cmd "-f $fdir/empty.txt"
+} {}
+
+test file_empty_str {<string> is empty} {
+    ly_cmd "-f $fdir/empty_str.txt"
+} {}
+
+test file_empty_str_err {empty <string> is not allowed} {
+    ly_cmd_err "-f $fdir/empty_str_err.txt" "does not conform"
+} {}
+
+test file_one_pattern {one pattern in the file} {
+    ly_cmd "-f $fdir/one_pattern.txt"
+} {}
+
+test file_two_patterns {two patterns in the file} {
+    ly_cmd "-f $fdir/two_patterns.txt"
+} {}
+
+test file_two_patterns_err {two patterns and the <string> is wrong} {
+    ly_cmd_err "-f $fdir/two_patterns_err.txt" "does not conform"
+} {}
+
+test file_two_patterns_invert_match {one pattern is inverted} {
+    ly_cmd "-f $fdir/two_patterns_invert_match.txt"
+} {}
+
+test file_two_patterns_invert_match_err {one pattern is inverted and the <string> is wrong} {
+    ly_cmd_err "-f $fdir/two_patterns_invert_match_err.txt" "does not conform to inverted"
+} {}
+
+cleanupTests
diff --git a/tests/yangre/files/empty.txt b/tests/yangre/files/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/yangre/files/empty.txt
diff --git a/tests/yangre/files/empty_str.txt b/tests/yangre/files/empty_str.txt
new file mode 100644
index 0000000..bf9b1b5
--- /dev/null
+++ b/tests/yangre/files/empty_str.txt
@@ -0,0 +1,2 @@
+"[0-9a-fA-F]*"
+
diff --git a/tests/yangre/files/empty_str_err.txt b/tests/yangre/files/empty_str_err.txt
new file mode 100644
index 0000000..f48a15d
--- /dev/null
+++ b/tests/yangre/files/empty_str_err.txt
@@ -0,0 +1,2 @@
+"[0-9a-fA-F]+"
+
diff --git a/tests/yangre/files/one_pattern.txt b/tests/yangre/files/one_pattern.txt
new file mode 100644
index 0000000..cf9acc5
--- /dev/null
+++ b/tests/yangre/files/one_pattern.txt
@@ -0,0 +1,3 @@
+"[0-9a-fA-F]*"
+
+1F
diff --git a/tests/yangre/files/two_patterns.txt b/tests/yangre/files/two_patterns.txt
new file mode 100644
index 0000000..7d04b2c
--- /dev/null
+++ b/tests/yangre/files/two_patterns.txt
@@ -0,0 +1,4 @@
+"[0-9a-fA-F]*"
+'[a-zA-Z0-9\-_.]*'
+
+1F
diff --git a/tests/yangre/files/two_patterns_err.txt b/tests/yangre/files/two_patterns_err.txt
new file mode 100644
index 0000000..78f9878
--- /dev/null
+++ b/tests/yangre/files/two_patterns_err.txt
@@ -0,0 +1,4 @@
+"[0-9a-fA-F]*"
+'[a-zA-Z0-9\-_.]*'
+
+@!@
diff --git a/tests/yangre/files/two_patterns_invert_match.txt b/tests/yangre/files/two_patterns_invert_match.txt
new file mode 100644
index 0000000..ffbd835
--- /dev/null
+++ b/tests/yangre/files/two_patterns_invert_match.txt
@@ -0,0 +1,4 @@
+"[a-z]*"
+ '[a-f]*'
+
+gh
diff --git a/tests/yangre/files/two_patterns_invert_match_err.txt b/tests/yangre/files/two_patterns_invert_match_err.txt
new file mode 100644
index 0000000..f182aab
--- /dev/null
+++ b/tests/yangre/files/two_patterns_invert_match_err.txt
@@ -0,0 +1,4 @@
+"[a-z]*"
+ '[a-f]*'
+
+ab
diff --git a/tests/yangre/invert_match.test b/tests/yangre/invert_match.test
new file mode 100644
index 0000000..707ca9d
--- /dev/null
+++ b/tests/yangre/invert_match.test
@@ -0,0 +1,28 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+test invert_match_from_help1 {Test the first pattern from help via invert match} {
+    ly_cmd_err {-p {"[0-9a-fA-F]*"} -i {1F}} "not conform to inverted"
+    ly_cmd {-p {"[0-9a-fA-F]*"} -i {GUN}}
+} {}
+
+test invert_match_from_help2 {Test the second pattern from help via invert match} {
+    ly_cmd_err {-p {'[a-zA-Z0-9\-_.]*'} -i {a-b}} "not conform to inverted"
+    ly_cmd {-p {'[a-zA-Z0-9\-_.]*'} -i {%@}}
+} {}
+
+test invert_match_from_help3 {Test the third pattern from help via invert match} {
+    ly_cmd_err {-p {[xX][mM][lL].*} -i {xml-encoding}} "not conform to inverted"
+    ly_cmd {-p {[xX][mM][lL].*} -i {json}}
+} {}
+
+test invert_match_three_at_once {Test three inverted patterns and once} {
+    ly_cmd_err {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {xml}} "not conform to inverted"
+    ly_cmd {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {%@}}
+} {}
+
+test invert_match_second_is_not {Test three patterns but the second one is not inverted} {
+    ly_cmd_err {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {o_O}} "not conform to inverted"
+    ly_cmd {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -p {[xX][mM][lL].*} -i {o_O}}
+} {}
+
+cleanupTests
diff --git a/tests/yangre/ly.tcl b/tests/yangre/ly.tcl
new file mode 100644
index 0000000..3bb62b5
--- /dev/null
+++ b/tests/yangre/ly.tcl
@@ -0,0 +1,17 @@
+# @brief The main source of functions and variables for testing yangre.
+
+package require tcltest
+namespace import ::tcltest::test ::tcltest::cleanupTests
+
+if { ![info exists ::env(TESTS_DIR)] } {
+    # the script is not run via 'ctest' so paths must be set
+    set ::env(TESTS_DIR) "./"
+    set TUT_PATH "../../build"
+} else {
+    # cmake (ctest) already sets ::env variables
+    set TUT_PATH $::env(YANGRE)
+}
+set TUT_NAME "yangre"
+source "$::env(TESTS_DIR)/../tool_ni.tcl"
+
+# The script continues by defining variables and functions specific to the yangre tool.
diff --git a/tests/yangre/pattern.test b/tests/yangre/pattern.test
new file mode 100644
index 0000000..45b7e3b
--- /dev/null
+++ b/tests/yangre/pattern.test
@@ -0,0 +1,19 @@
+source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}]
+
+test pattern_from_help1 {Test the first pattern from help} {
+    ly_cmd {-p {"[0-9a-fA-F]*"} {1F}}
+} {}
+
+test pattern_from_help2 {Test the second pattern from help} {
+    ly_cmd {-p {'[a-zA-Z0-9\-_.]*'} {a-b}}
+} {}
+
+test pattern_from_help3 {Test the third pattern from help} {
+    ly_cmd {-p {[xX][mM][lL].*} {xml-encoding}}
+} {}
+
+test pattern_three_at_once {Test three patterns and once} {
+    ly_cmd {-p {"[0-9a-zA-Z]*"} -p {'[a-zA-Z0-9\-_.]*'} -p {[xX][mM][lL].*} {xml}}
+} {}
+
+cleanupTests
diff --git a/tools/lint/CMakeLists.txt b/tools/lint/CMakeLists.txt
index 32cdcbf..14f8b76 100644
--- a/tools/lint/CMakeLists.txt
+++ b/tools/lint/CMakeLists.txt
@@ -17,6 +17,12 @@
     cmd_load.c
     cmd_print.c
     cmd_searchpath.c
+    cmd_extdata.c
+    cmd_help.c
+    cmd_verb.c
+    cmd_debug.c
+    yl_opt.c
+    yl_schema_features.c
     common.c
 )
 if(YANGLINT_INTERACTIVE)
@@ -46,45 +52,3 @@
     target_include_directories(yanglint PRIVATE ${GETOPT_INCLUDE_DIR})
     target_link_libraries(yanglint ${GETOPT_LIBRARY})
 endif()
-
-#
-# tests
-#
-function(add_yanglint_test)
-    cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN})
-    set(TEST_NAME yanglint_${ADDTEST_NAME})
-
-    if(${ADDTEST_VIA} STREQUAL "bash")
-        set(WRAPPER /usr/bin/env bash)
-    elseif(${ADDTEST_VIA} STREQUAL "expect")
-        set(WRAPPER ${PATH_EXPECT})
-    else()
-        message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'")
-    endif()
-
-    add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/tests/${ADDTEST_SCRIPT})
-    set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}/yanglint")
-    set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${PROJECT_SOURCE_DIR}/tests/modules/yang")
-    set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
-endfunction(add_yanglint_test)
-
-if(ENABLE_TESTS)
-    # tests of non-interactive mode using shunit2
-    find_program(PATH_SHUNIT NAMES shunit2)
-    if(NOT PATH_SHUNIT)
-        message(WARNING "'shunit2' not found! The yanglint(1) non-interactive tests will not be available.")
-    else()
-        add_yanglint_test(NAME ni_list VIA bash SCRIPT shunit2/list.sh)
-        add_yanglint_test(NAME ni_feature VIA bash SCRIPT shunit2/feature.sh)
-    endif()
-
-    # tests of interactive mode using expect
-    find_program(PATH_EXPECT NAMES expect)
-    if(NOT PATH_EXPECT)
-        message(WARNING "'expect' not found! The yanglint(1) interactive tests will not be available.")
-    elseif(YANGLINT_INTERACTIVE)
-        add_yanglint_test(NAME in_list VIA expect SCRIPT expect/list.exp)
-        add_yanglint_test(NAME in_feature VIA expect SCRIPT expect/feature.exp)
-        add_yanglint_test(NAME in_completion VIA expect SCRIPT expect/completion.exp)
-    endif()
-endif()
diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c
index 10e7446..344900d 100644
--- a/tools/lint/cmd.c
+++ b/tools/lint/cmd.c
@@ -2,9 +2,10 @@
  * @file cmd.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's yanglint tool general commands
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -30,230 +31,80 @@
 COMMAND commands[];
 extern int done;
 
-#ifndef NDEBUG
-
 void
-cmd_debug_help(void)
+cmd_free(void)
 {
-    printf("Usage: debug (dict | xpath)+\n");
-}
+    uint16_t i;
 
-void
-cmd_debug(struct ly_ctx **UNUSED(ctx), const char *cmdline)
-{
-    int argc = 0;
-    char **argv = NULL;
-    int opt, opt_index;
-    struct option options[] = {
-        {"help", no_argument, NULL, 'h'},
-        {NULL, 0, NULL, 0}
-    };
-    uint32_t dbg_groups = 0;
-
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
-    }
-
-    while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
-        switch (opt) {
-        case 'h':
-            cmd_debug_help();
-            goto cleanup;
-        default:
-            YLMSG_E("Unknown option.\n");
-            goto cleanup;
+    for (i = 0; commands[i].name; i++) {
+        if (commands[i].free_func) {
+            commands[i].free_func();
         }
     }
-    if (argc == optind) {
-        /* no argument */
-        cmd_debug_help();
-        goto cleanup;
-    }
-
-    for (int i = 0; i < argc - optind; i++) {
-        if (!strcasecmp("dict", argv[optind + i])) {
-            dbg_groups |= LY_LDGDICT;
-        } else if (!strcasecmp("xpath", argv[optind + i])) {
-            dbg_groups |= LY_LDGXPATH;
-        } else {
-            YLMSG_E("Unknown debug group \"%s\"\n", argv[optind + 1]);
-            goto cleanup;
-        }
-    }
-
-    ly_log_dbg_groups(dbg_groups);
-
-cleanup:
-    free_cmdline(argv);
 }
 
-#endif
-
-void
-cmd_verb_help(void)
-{
-    printf("Usage: verb (error | warning | verbose | debug)\n");
-}
-
-void
-cmd_verb(struct ly_ctx **UNUSED(ctx), const char *cmdline)
-{
-    int argc = 0;
-    char **argv = NULL;
-    int opt, opt_index;
-    struct option options[] = {
-        {"help", no_argument, NULL, 'h'},
-        {NULL, 0, NULL, 0}
-    };
-
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
-    }
-
-    while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
-        switch (opt) {
-        case 'h':
-            cmd_verb_help();
-            goto cleanup;
-        default:
-            YLMSG_E("Unknown option.\n");
-            goto cleanup;
-        }
-    }
-
-    if (argc - optind > 1) {
-        YLMSG_E("Only a single verbosity level can be set.\n");
-        cmd_verb_help();
-        goto cleanup;
-    } else if (argc == optind) {
-        /* no argument - print current value */
-        LY_LOG_LEVEL level = ly_log_level(LY_LLERR);
-
-        ly_log_level(level);
-        printf("Current verbosity level: ");
-        if (level == LY_LLERR) {
-            printf("error\n");
-        } else if (level == LY_LLWRN) {
-            printf("warning\n");
-        } else if (level == LY_LLVRB) {
-            printf("verbose\n");
-        } else if (level == LY_LLDBG) {
-            printf("debug\n");
-        }
-        goto cleanup;
-    }
-
-    if (!strcasecmp("error", argv[optind]) || !strcmp("0", argv[optind])) {
-        ly_log_level(LY_LLERR);
-    } else if (!strcasecmp("warning", argv[optind]) || !strcmp("1", argv[optind])) {
-        ly_log_level(LY_LLWRN);
-    } else if (!strcasecmp("verbose", argv[optind]) || !strcmp("2", argv[optind])) {
-        ly_log_level(LY_LLVRB);
-    } else if (!strcasecmp("debug", argv[optind]) || !strcmp("3", argv[optind])) {
-        ly_log_level(LY_LLDBG);
-    } else {
-        YLMSG_E("Unknown verbosity \"%s\"\n", argv[optind]);
-        goto cleanup;
-    }
-
-cleanup:
-    free_cmdline(argv);
-}
-
-void
-cmd_quit(struct ly_ctx **UNUSED(ctx), const char *UNUSED(cmdline))
+int
+cmd_quit_exec(struct ly_ctx **UNUSED(ctx), struct yl_opt *UNUSED(yo), const char *UNUSED(posv))
 {
     done = 1;
-    return;
+    return 0;
 }
 
-void
-cmd_help_help(void)
-{
-    printf("Usage: help [cmd ...]\n");
-}
-
-void
-cmd_help(struct ly_ctx **UNUSED(ctx), const char *cmdline)
-{
-    int argc = 0;
-    char **argv = NULL;
-    int opt, opt_index;
-    struct option options[] = {
-        {"help", no_argument, NULL, 'h'},
-        {NULL, 0, NULL, 0}
-    };
-
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
-    }
-
-    while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) {
-        switch (opt) {
-        case 'h':
-            cmd_help_help();
-            goto cleanup;
-        default:
-            YLMSG_E("Unknown option.\n");
-            goto cleanup;
-        }
-    }
-
-    if (argc == optind) {
-generic_help:
-        printf("Available commands:\n");
-        for (uint16_t i = 0; commands[i].name; i++) {
-            if (commands[i].helpstring != NULL) {
-                printf("  %-15s %s\n", commands[i].name, commands[i].helpstring);
-            }
-        }
-    } else {
-        /* print specific help for the selected command(s) */
-
-        for (int c = 0; c < argc - optind; ++c) {
-            int8_t match = 0;
-
-            /* get the command of the specified name */
-            for (uint16_t i = 0; commands[i].name; i++) {
-                if (strcmp(argv[optind + c], commands[i].name) == 0) {
-                    match = 1;
-                    if (commands[i].help_func != NULL) {
-                        commands[i].help_func();
-                    } else {
-                        printf("%s: %s\n", argv[optind + c], commands[i].helpstring);
-                    }
-                    break;
-                }
-            }
-            if (!match) {
-                /* if unknown command specified, print the list of commands */
-                printf("Unknown command \'%s\'\n", argv[optind + c]);
-                goto generic_help;
-            }
-        }
-    }
-
-cleanup:
-    free_cmdline(argv);
-}
-
+/* Also keep enum COMMAND_INDEX updated. */
 COMMAND commands[] = {
-    {"help", cmd_help, cmd_help_help, "Display commands description"},
-    {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"},
-    {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"},
-    {"print", cmd_print, cmd_print_help, "Print a module"},
-    {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"},
-    {"list", cmd_list, cmd_list_help, "List all the loaded modules"},
-    {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"},
-    {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"},
-    {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"},
-    {"verb", cmd_verb, cmd_verb_help, "Change verbosity"},
+    {
+        "help", cmd_help_opt, NULL, cmd_help_exec, NULL, cmd_help_help, NULL,
+        "Display commands description", "h"
+    },
+    {
+        "add", cmd_add_opt, cmd_add_dep, cmd_add_exec, NULL, cmd_add_help, NULL,
+        "Add a new module from a specific file", "DF:hiX"
+    },
+    {
+        "load", cmd_load_opt, cmd_load_dep, cmd_load_exec, NULL, cmd_load_help, NULL,
+        "Load a new schema from the searchdirs", "F:hiX"
+    },
+    {
+        "print", cmd_print_opt, cmd_print_dep, cmd_print_exec, NULL, cmd_print_help, NULL,
+        "Print a module", "f:hL:o:P:q"
+    },
+    {
+        "data", cmd_data_opt, cmd_data_dep, cmd_data_store, cmd_data_process, cmd_data_help, NULL,
+        "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:"
+    },
+    {
+        "list", cmd_list_opt, cmd_list_dep, cmd_list_exec, NULL, cmd_list_help, NULL,
+        "List all the loaded modules", "f:h"
+    },
+    {
+        "feature", cmd_feature_opt, cmd_feature_dep, cmd_feature_exec, cmd_feature_fin, cmd_feature_help, NULL,
+        "Print all features of module(s) with their state", "haf"
+    },
+    {
+        "searchpath", cmd_searchpath_opt, NULL, cmd_searchpath_exec, NULL, cmd_searchpath_help, NULL,
+        "Print/set the search path(s) for schemas", "ch"
+    },
+    {
+        "extdata", cmd_extdata_opt, cmd_extdata_dep, cmd_extdata_exec, NULL, cmd_extdata_help, cmd_extdata_free,
+        "Set the specific data required by an extension", "ch"
+    },
+    {
+        "clear", cmd_clear_opt, cmd_clear_dep, cmd_clear_exec, NULL, cmd_clear_help, NULL,
+        "Clear the context - remove all the loaded modules", "iyhY:"
+    },
+    {
+        "verb", cmd_verb_opt, cmd_verb_dep, cmd_verb_exec, NULL, cmd_verb_help, NULL,
+        "Change verbosity", "h"
+    },
 #ifndef NDEBUG
-    {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"},
+    {
+        "debug", cmd_debug_opt, cmd_debug_dep, cmd_debug_store, cmd_debug_setlog, cmd_debug_help, NULL,
+        "Display specific debug message groups", "h"
+    },
 #endif
-    {"quit", cmd_quit, NULL, "Quit the program"},
+    {"quit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"},
     /* synonyms for previous commands */
-    {"?", cmd_help, NULL, "Display commands description"},
-    {"exit", cmd_quit, NULL, "Quit the program"},
-    {NULL, NULL, NULL, NULL}
+    {"?", NULL, NULL, cmd_help_exec, NULL, NULL, NULL, "Display commands description", "h"},
+    {"exit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"},
+    {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
 };
diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h
index 9f6f88d..bd2f2f2 100644
--- a/tools/lint/cmd.h
+++ b/tools/lint/cmd.h
@@ -2,9 +2,10 @@
  * @file cmd.h
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's yanglint tool commands header
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -18,15 +19,44 @@
 
 #include "libyang.h"
 
+struct yl_opt;
+
 /**
  * @brief command information
+ *
+ * Callback functions are in the order they should be called.
+ * First, the 'opt_func' should be called, which parses arguments from the command line and sets flags or pointers in
+ * the struct yl_opt. This type of function is for interactive mode and is optional.
+ * Then the 'dep_func' callback can check the struct yl_opt settings. Other items that depend on them can also be
+ * set. There is also an possibility for controlling the number of positional arguments and its implications.
+ * The most important callback is 'exec_func' where the command itself is executed. This function can even replace the
+ * entire libyang context. The function parameters are mainly found in the yl_opt structure. Optionally, the function
+ * can be called with a positional argument obtained from the command line. Some 'exec_func' are adapted to be called
+ * from non-interactive yanglint mode.
+ * The 'fun_func' complements the 'exec_func'. In some cases, the command execution must be divided into two stages.
+ * For example, the 'exec_func' is used to fill some items in the yl_opt structure from the positional
+ * arguments and then the 'fin_func' is used to perform the final action.
  */
 typedef struct {
-    char *name;                                      /* User printable name of the function. */
+    /* User printable name of the function. */
+    char *name;
 
-    void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */
-    void (*help_func)(void);                         /* Display command help. */
-    char *helpstring;                                /* Documentation for this function. */
+    /* Convert command line options to the data struct yl_opt. */
+    int (*opt_func)(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+    /* Additionally set dependent items and perform error checking. */
+    int (*dep_func)(struct yl_opt *yo, int posc);
+    /* Execute command. */
+    int (*exec_func)(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+    /* Finish execution of command. */
+    int (*fin_func)(struct ly_ctx *ctx, struct yl_opt *yo);
+    /* Display command help. */
+    void (*help_func)(void);
+    /* Freeing global variables allocated by the command. */
+    void (*free_func)(void);
+    /* Documentation for this function. */
+    char *helpstring;
+    /* Option characters used in function getopt_long. */
+    char *optstring;
 } COMMAND;
 
 /**
@@ -34,36 +64,333 @@
  */
 extern COMMAND commands[];
 
+/**
+ * @brief Index for global variable ::commands.
+ */
+enum COMMAND_INDEX {
+    CMD_HELP = 0,
+    CMD_ADD,
+    CMD_LOAD,
+    CMD_PRINT,
+    CMD_DATA,
+    CMD_LIST,
+    CMD_FEATURE,
+    CMD_SEARCHPATH,
+    CMD_EXTDATA,
+    CMD_CLEAR,
+    CMD_VERB,
+#ifndef NDEBUG
+    CMD_DEBUG,
+#endif
+};
+
+/**
+ * @brief For each cmd, call the COMMAND.free_func in the variable 'commands'.
+ */
+void cmd_free(void);
+
 /* cmd_add.c */
-void cmd_add(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @brief Parse the arguments of an interactive command.
+ *
+ * @param[out] yo Context for yanglint.
+ * @param[in] cmdline String containing command line arguments.
+ * @param[out] posv Pointer to argv to a section of positional arguments.
+ * @param[out] posc Number of positional arguments.
+ * @return 0 on success.
+ */
+int cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @brief Check the options and set dependent items in @p yo.
+ *
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posc number of positional arguments.
+ * @return 0 on success.
+ */
+int cmd_add_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Parse and compile a new module using filepath.
+ *
+ * @param[in,out] ctx Context for libyang.
+ * @param[in,out] yo Context for yanglint.
+ * @param[in] posv Path to the file where the new module is located.
+ * @return 0 on success.
+ */
+int cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
 void cmd_add_help(void);
 
 /* cmd_clear.c */
-void cmd_clear(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_clear_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Clear libyang context.
+ *
+ * @param[in,out] ctx context for libyang that will be replaced with an empty one.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv not used.
+ * @return 0 on success.
+ */
+int cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
 void cmd_clear_help(void);
 
 /* cmd_data.c */
-void cmd_data(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_data_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Store data file for later processing.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Path to the file where the data is located.
+ * @return 0 on success.
+ */
+int cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+
+/**
+ * @brief Parse, validate and optionally print data instances.
+ *
+ * @param[in] ctx Context for libyang.
+ * @param[in] yo Context of yanglint. All necessary parameters should already be set.
+ * @return 0 on success.
+ */
+int cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo);
 void cmd_data_help(void);
 
 /* cmd_list.c */
-void cmd_list(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_list_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Print the list of modules in the current context.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Not used.
+ * @return 0 on success.
+ */
+int cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
 void cmd_list_help(void);
 
 /* cmd_feature.c */
-void cmd_feature(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_feature_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Print the features the modules.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the module which features are to be printed.
+ * @return 0 on success.
+ */
+int cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+
+/**
+ * @brief Printing of features ends.
+ *
+ * @param[in] ctx context for libyang. Not used.
+ * @param[in] yo context for yanglint.
+ * @return 0 on success.
+ */
+int cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo);
 void cmd_feature_help(void);
 
 /* cmd_load.c */
-void cmd_load(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_load_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Parse and compile a new module using module name.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the module to be loaded into the context.
+ * @return 0 on success.
+ */
+int cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
 void cmd_load_help(void);
 
 /* cmd_print.c */
-void cmd_print(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_print_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Print a schema module.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the module to be printed. Can be NULL in the case of printing a node.
+ * @return 0 on success.
+ */
+int cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
 void cmd_print_help(void);
 
 /* cmd_searchpath.c */
-void cmd_searchpath(struct ly_ctx **ctx, const char *cmdline);
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @brief Set the paths of directories where to search schema modules.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Path to the directory. Can be NULL in the case of printing a current searchdirs.
+ * @return 0 on success.
+ */
+int cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
 void cmd_searchpath_help(void);
 
+/* cmd_extdata.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_extdata_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Set path to the file required by the extension.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Path to the directory. Can be NULL in the case of printing a current path.
+ * @return 0 on success.
+ */
+int cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+void cmd_extdata_help(void);
+void cmd_extdata_free(void);
+
+/* cmd_help.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @brief Print help.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the command which help message is to be printed. Can be NULL.
+ * @return 0 on success.
+ */
+int cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+void cmd_help_help(void);
+
+/* cmd_verb.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_verb_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Set the verbose level.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the verbose level to be set.
+ * @return 0 on success.
+ */
+int cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+void cmd_verb_help(void);
+
+/* cmd_debug.c */
+
+/**
+ * @copydoc cmd_add_opt
+ */
+int cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc);
+
+/**
+ * @copydoc cmd_add_dep
+ */
+int cmd_debug_dep(struct yl_opt *yo, int posc);
+
+/**
+ * @brief Store the type of debug messages for later processing.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint.
+ * @param[in] posv Name of the debug type to be set.
+ * @return 0 on success.
+ */
+int cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv);
+
+/**
+ * @brief Set debug logging.
+ *
+ * @param[in,out] ctx context for libyang.
+ * @param[in,out] yo context for yanglint. All necessary parameters should already be set.
+ * @return 0 on success.
+ */
+int cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo);
+void cmd_debug_help(void);
+
 #endif /* COMMANDS_H_ */
diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c
index bbfdfd6..6a9af8d 100644
--- a/tools/lint/cmd_add.c
+++ b/tools/lint/cmd_add.c
@@ -2,9 +2,10 @@
  * @file cmd_add.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'add' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -17,8 +18,8 @@
 
 #include "cmd.h"
 
+#include <assert.h>
 #include <getopt.h>
-#include <libgen.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -26,6 +27,8 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
 
 void
 cmd_add_help(void)
@@ -44,133 +47,164 @@
             "  -i, --make-implemented\n"
             "                  Make the imported modules \"referenced\" from any loaded\n"
             "                  <schema> module also implemented. If specified a second time,\n"
-            "                  all the modules are set implemented.\n");
+            "                  all the modules are set implemented.\n"
+            "  -X, --extended-leafref\n"
+            "                  Allow usage of deref() XPath function within leafref.\n");
 }
 
-void
-cmd_add(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
+    int rc = 0, argc = 0;
     int opt, opt_index;
     struct option options[] = {
         {"disable-searchdir", no_argument, NULL, 'D'},
         {"features", required_argument, NULL, 'F'},
         {"help", no_argument, NULL, 'h'},
         {"make-implemented", no_argument, NULL, 'i'},
+        {"extended-leafref", no_argument, NULL, 'X'},
         {NULL, 0, NULL, 0}
     };
-    uint16_t options_ctx = 0;
-    const char *all_features[] = {"*", NULL};
-    struct ly_set fset = {0};
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "D:F:hi", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_ADD].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'D': /* --disable--search */
-            if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
+            if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
                 YLMSG_W("The -D option specified too many times.\n");
             }
-            if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) {
-                options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
-                options_ctx |= LY_CTX_DISABLE_SEARCHDIRS;
-            } else {
-                options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD;
-            }
+            yo_opt_update_disable_searchdir(yo);
             break;
 
         case 'F': /* --features */
-            if (parse_features(optarg, &fset)) {
-                goto cleanup;
+            if (parse_features(optarg, &yo->schema_features)) {
+                return 1;
             }
             break;
 
         case 'h':
             cmd_add_help();
-            goto cleanup;
+            return 1;
 
         case 'i': /* --make-implemented */
-            if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
-                options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
-                options_ctx |= LY_CTX_ALL_IMPLEMENTED;
-            } else {
-                options_ctx |= LY_CTX_REF_IMPLEMENTED;
-            }
+            yo_opt_update_make_implemented(yo);
+            break;
+
+        case 'X': /* --extended-leafref */
+            yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
             break;
 
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (argc == optind) {
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_add_dep(struct yl_opt *yo, int posc)
+{
+    if (yo->interactive && !posc) {
         /* no argument */
         cmd_add_help();
+        return 1;
+    }
+    if (!yo->schema_features.count) {
+        /* no features, enable all of them */
+        yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES;
+    }
+
+    return 0;
+}
+
+static int
+store_parsed_module(const char *filepath, struct lys_module *mod, struct yl_opt *yo)
+{
+    assert(!yo->interactive);
+
+    if (yo->schema_out_format || yo->feature_param_format) {
+        if (ly_set_add(&yo->schema_modules, (void *)mod, 1, NULL)) {
+            YLMSG_E("Storing parsed schema module (%s) for print failed.\n", filepath);
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+int
+cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    const char *all_features[] = {"*", NULL};
+    LY_ERR ret;
+    uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
+    char *dir, *modname = NULL;
+    const char **features = NULL;
+    struct ly_in *in = NULL;
+    struct lys_module *mod;
+
+    assert(posv);
+
+    if (yo->ctx_options) {
+        ly_ctx_set_options(*ctx, yo->ctx_options);
+    }
+
+    if (parse_schema_path(posv, &dir, &modname)) {
+        return 1;
+    }
+
+    if (!yo->interactive) {
+        /* The module should already be parsed if yang_lib_file was set. */
+        if (yo->yang_lib_file && (mod = ly_ctx_get_module_implemented(*ctx, modname))) {
+            ret = store_parsed_module(posv, mod, yo);
+            goto cleanup;
+        }
+        /* parse module */
+    }
+
+    /* add temporarily also the path of the module itself */
+    if (ly_ctx_set_searchdir(*ctx, dir) == LY_EEXIST) {
+        path_unset = 0;
+    }
+
+    /* get features list for this module */
+    if (!yo->schema_features.count) {
+        features = all_features;
+    } else {
+        get_features(&yo->schema_features, modname, &features);
+    }
+
+    /* prepare input handler */
+    ret = ly_in_new_filepath(posv, 0, &in);
+    if (ret) {
         goto cleanup;
     }
 
-    if (!fset.count) {
-        /* no features, enable all of them */
-        options_ctx |= LY_CTX_ENABLE_IMP_FEATURES;
+    /* parse the file */
+    ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, &mod);
+    ly_in_free(in, 1);
+    ly_ctx_unset_searchdir_last(*ctx, path_unset);
+    if (ret) {
+        goto cleanup;
     }
 
-    if (options_ctx) {
-        ly_ctx_set_options(*ctx, options_ctx);
-    }
-
-    for (int i = 0; i < argc - optind; i++) {
-        /* process the schema module files */
-        LY_ERR ret;
-        uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
-        char *dir, *module;
-        const char **features = NULL;
-        struct ly_in *in = NULL;
-
-        if (parse_schema_path(argv[optind + i], &dir, &module)) {
-            goto cleanup;
-        }
-
-        /* add temporarily also the path of the module itself */
-        if (ly_ctx_set_searchdir(*ctx, dirname(dir)) == LY_EEXIST) {
-            path_unset = 0;
-        }
-
-        /* get features list for this module */
-        if (!fset.count) {
-            features = all_features;
-        } else {
-            get_features(&fset, module, &features);
-        }
-
-        /* temporary cleanup */
-        free(dir);
-        free(module);
-
-        /* prepare input handler */
-        ret = ly_in_new_filepath(argv[optind + i], 0, &in);
-        if (ret) {
-            goto cleanup;
-        }
-
-        /* parse the file */
-        ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, NULL);
-        ly_in_free(in, 1);
-        ly_ctx_unset_searchdir_last(*ctx, path_unset);
-
-        if (ret) {
-            /* libyang printed the error messages */
+    if (!yo->interactive) {
+        if ((ret = store_parsed_module(posv, mod, yo))) {
             goto cleanup;
         }
     }
 
 cleanup:
-    if (options_ctx) {
-        ly_ctx_unset_options(*ctx, options_ctx);
-    }
-    ly_set_erase(&fset, free_features);
-    free_cmdline(argv);
+    free(dir);
+    free(modname);
+
+    return ret;
 }
diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c
index 5eed6ff..233cc92 100644
--- a/tools/lint/cmd_clear.c
+++ b/tools/lint/cmd_clear.c
@@ -2,9 +2,10 @@
  * @file cmd_clear.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'clear' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
 
 void
 cmd_clear_help(void)
@@ -32,68 +34,139 @@
             "                  Replace the current context with an empty one, searchpaths\n"
             "                  are not kept.\n\n"
             "  -i, --make-implemented\n"
-            "                Make the imported modules \"referenced\" from any loaded\n"
-            "                module also implemented. If specified a second time, all the\n"
-            "                modules are set implemented.\n"
+            "                When loading a module into the context, the imported 'referenced'\n"
+            "                modules will also be implemented. If specified a second time,\n"
+            "                all the modules will be implemented.\n"
             "  -y, --yang-library\n"
             "                  Load and implement internal \"ietf-yang-library\" YANG module.\n"
             "                  Note that this module includes definitions of mandatory state\n"
-            "                  data that can result in unexpected data validation errors.\n");
-#if 0
-    "                  If <yang-library-data> path specified, load the modules\n"
-    "                  according to the provided yang library data.\n"
-#endif
+            "                  data that can result in unexpected data validation errors.\n"
+            "  -Y FILE, --yang-library-file=FILE\n"
+            "                Parse FILE with \"ietf-yang-library\" data and use them to\n"
+            "                create an exact YANG schema context. Searchpaths defined so far\n"
+            "                are used, but then deleted.\n");
 }
 
-void
-cmd_clear(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
+    int rc = 0, argc = 0;
     int opt, opt_index;
     struct option options[] = {
-        {"make-implemented", no_argument, NULL, 'i'},
-        {"yang-library",     no_argument, NULL, 'y'},
+        {"make-implemented",    no_argument, NULL, 'i'},
+        {"yang-library",        no_argument, NULL, 'y'},
+        {"yang-library-file",   no_argument, NULL, 'Y'},
         {"help",             no_argument, NULL, 'h'},
         {NULL, 0, NULL, 0}
     };
-    uint16_t options_ctx = LY_CTX_NO_YANGLIBRARY;
-    struct ly_ctx *ctx_new;
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    yo->ctx_options = LY_CTX_NO_YANGLIBRARY;
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_CLEAR].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'i':
-            if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
-                options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
-                options_ctx |= LY_CTX_ALL_IMPLEMENTED;
+            if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) {
+                yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
+                yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
             } else {
-                options_ctx |= LY_CTX_REF_IMPLEMENTED;
+                yo->ctx_options |= LY_CTX_REF_IMPLEMENTED;
             }
             break;
         case 'y':
-            options_ctx &= ~LY_CTX_NO_YANGLIBRARY;
+            yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+            break;
+        case 'Y':
+            yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+            yo->yang_lib_file = optarg;
+            if (!yo->yang_lib_file) {
+                YLMSG_E("Memory allocation error.\n");
+                return 1;
+            }
             break;
         case 'h':
             cmd_clear_help();
-            goto cleanup;
+            return 1;
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (ly_ctx_new(NULL, options_ctx, &ctx_new)) {
-        YLMSG_W("Failed to create context.\n");
-        goto cleanup;
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return rc;
+}
+
+int
+cmd_clear_dep(struct yl_opt *yo, int posc)
+{
+    (void) yo;
+
+    if (posc) {
+        YLMSG_E("No positional arguments are allowed.\n");
+        return 1;
     }
 
+    return 0;
+}
+
+/**
+ * @brief Convert searchpaths into single string.
+ *
+ * @param[in] ctx Context with searchpaths.
+ * @param[out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":"
+ * (on Windows, used semicolon ";" instead).
+ * @return LY_ERR value.
+ */
+static LY_ERR
+searchpaths_to_str(const struct ly_ctx *ctx, char **searchpaths)
+{
+    uint32_t i;
+    int rc = 0;
+    const char * const *dirs = ly_ctx_get_searchdirs(ctx);
+
+    for (i = 0; dirs[i]; ++i) {
+        rc = searchpath_strcat(searchpaths, dirs[i]);
+        if (!rc) {
+            break;
+        }
+    }
+
+    return rc;
+}
+
+int
+cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    (void) posv;
+    struct ly_ctx *ctx_new = NULL;
+
+    if (yo->yang_lib_file) {
+        if (searchpaths_to_str(*ctx, &yo->searchpaths)) {
+            YLMSG_E("Storing searchpaths failed.\n");
+            return 1;
+        }
+        if (ly_ctx_new_ylpath(yo->searchpaths, yo->yang_lib_file, LYD_UNKNOWN, yo->ctx_options, &ctx_new)) {
+            YLMSG_E("Unable to create libyang context with yang-library data.\n");
+            return 1;
+        }
+    } else {
+        if (ly_ctx_new(NULL, yo->ctx_options, &ctx_new)) {
+            YLMSG_W("Failed to create context.\n");
+            return 1;
+        }
+    }
+
+    /* Global variables in commands are also deleted. */
+    cmd_free();
+
     ly_ctx_destroy(*ctx);
     *ctx = ctx_new;
 
-cleanup:
-    free_cmdline(argv);
+    return 0;
 }
diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c
index 69f94bc..9fa1b95 100644
--- a/tools/lint/cmd_data.c
+++ b/tools/lint/cmd_data.c
@@ -2,9 +2,10 @@
  * @file cmd_data.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'data' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@
 
 #include "cmd.h"
 
+#include <assert.h>
 #include <errno.h>
 #include <getopt.h>
 #include <stdint.h>
@@ -27,18 +29,23 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
 
-void
-cmd_data_help(void)
+static void
+cmd_data_help_header(void)
 {
     printf("Usage: data [-emn] [-t TYPE]\n"
             "            [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n"
             "       data [-n] -t (rpc | notif | reply) [-O FILE]\n"
             "            [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n"
             "       data [-en] [-t TYPE] [-F FORMAT] -x XPATH [-o OUTFILE] <data1> ...\n"
-            "                  Parse, validate and optionally print data instances\n\n"
+            "                  Parse, validate and optionally print data instances\n");
+}
 
-            "  -t TYPE, --type=TYPE\n"
+static void
+cmd_data_help_type(void)
+{
+    printf("  -t TYPE, --type=TYPE\n"
             "                Specify data tree type in the input data file(s):\n"
             "        data          - Complete datastore with status data (default type).\n"
             "        config        - Configuration datastore (without status data).\n"
@@ -47,14 +54,73 @@
             "        edit          - Content of the NETCONF <edit-config> operation.\n"
             "        rpc           - Content of the NETCONF <rpc> message, defined as YANG's\n"
             "                        RPC/Action input statement.\n"
+            "        nc-rpc        - Similar to 'rpc' but expect and check also the NETCONF\n"
+            "                        envelopes <rpc> or <action>.\n"
             "        reply         - Reply to the RPC/Action. Note that the reply data are\n"
             "                        expected inside a container representing the original\n"
             "                        RPC/Action. This is necessary to identify appropriate\n"
             "                        data definitions in the schema module.\n"
+            "        nc-reply      - Similar to 'reply' but expect and check also the NETCONF\n"
+            "                        envelope <rpc-reply> with output data nodes as direct\n"
+            "                        descendants. The original RPC/action invocation is expected\n"
+            "                        in a separate parameter '-R' and is parsed as 'nc-rpc'.\n"
             "        notif         - Notification instance (content of the <notification>\n"
-            "                        element without <eventTime>).\n\n"
+            "                        element without <eventTime>).\n"
+            "        nc-notif      - Similar to 'notif' but expect and check also the NETCONF\n"
+            "                        envelope <notification> with element <eventTime> and its\n"
+            "                        sibling as the actual notification.\n");
+}
 
-            "  -e, --present Validate only with the schema modules whose data actually\n"
+static void
+cmd_data_help_format(void)
+{
+    printf("  -f FORMAT, --format=FORMAT\n"
+            "                Print the data in one of the following formats:\n"
+            "                xml, json, lyb\n"
+            "                Note that the LYB format requires the -o option specified.\n");
+}
+
+static void
+cmd_data_help_in_format(void)
+{
+    printf("  -F FORMAT, --in-format=FORMAT\n"
+            "                Load the data in one of the following formats:\n"
+            "                xml, json, lyb\n"
+            "                If input format not specified, it is detected from the file extension.\n");
+}
+
+static void
+cmd_data_help_default(void)
+{
+    printf("  -d MODE, --default=MODE\n"
+            "                Print data with default values, according to the MODE\n"
+            "                (to print attributes, ietf-netconf-with-defaults model\n"
+            "                must be loaded):\n"
+            "      all             - Add missing default nodes.\n"
+            "      all-tagged      - Add missing default nodes and mark all the default\n"
+            "                        nodes with the attribute.\n"
+            "      trim            - Remove all nodes with a default value.\n"
+            "      implicit-tagged - Add missing nodes and mark them with the attribute.\n");
+}
+
+static void
+cmd_data_help_xpath(void)
+{
+    printf("  -x XPATH, --xpath=XPATH\n"
+            "                Evaluate XPATH expression and print the nodes satisfying the\n"
+            "                expression. The output format is specific and the option cannot\n"
+            "                be combined with the -f and -d options. Also all the data\n"
+            "                inputs are merged into a single data tree where the expression\n"
+            "                is evaluated, so the -m option is always set implicitly.\n");
+}
+
+void
+cmd_data_help(void)
+{
+    cmd_data_help_header();
+    printf("\n");
+    cmd_data_help_type();
+    printf("  -e, --present Validate only with the schema modules whose data actually\n"
             "                exist in the provided input data files. Takes effect only\n"
             "                with the 'data' or 'config' TYPEs. Used to avoid requiring\n"
             "                mandatory nodes from modules which data are not present in the\n"
@@ -64,47 +130,28 @@
             "                In case of using -x option, the data are always merged.\n"
             "  -n, --not-strict\n"
             "                Do not require strict data parsing (silently skip unknown data),\n"
-            "                has no effect for schemas.\n\n"
+            "                has no effect for schemas.\n"
             "  -O FILE, --operational=FILE\n"
             "                Provide optional data to extend validation of the 'rpc',\n"
             "                'reply' or 'notif' TYPEs. The FILE is supposed to contain\n"
-            "                the :running configuration datastore and state data\n"
-            "                (operational datastore) referenced from the RPC/Notification.\n\n"
-
-            "  -f FORMAT, --format=FORMAT\n"
-            "                Print the data in one of the following formats:\n"
-            "                xml, json, lyb\n"
-            "                Note that the LYB format requires the -o option specified.\n"
-            "  -F FORMAT, --in-format=FORMAT\n"
-            "                Load the data in one of the following formats:\n"
-            "                xml, json, lyb\n"
-            "                If input format not specified, it is detected from the file extension.\n"
-            "  -d MODE, --default=MODE\n"
-            "                Print data with default values, according to the MODE\n"
-            "                (to print attributes, ietf-netconf-with-defaults model\n"
-            "                must be loaded):\n"
-            "      all             - Add missing default nodes.\n"
-            "      all-tagged      - Add missing default nodes and mark all the default\n"
-            "                        nodes with the attribute.\n"
-            "      trim            - Remove all nodes with a default value.\n"
-            "      implicit-tagged - Add missing nodes and mark them with the attribute.\n"
-            "  -o OUTFILE, --output=OUTFILE\n"
-            "                Write the output to OUTFILE instead of stdout.\n\n"
-
-            "  -x XPATH, --xpath=XPATH\n"
-            "                Evaluate XPATH expression and print the nodes satysfying the.\n"
-            "                expression. The output format is specific and the option cannot\n"
-            "                be combined with the -f and -d options. Also all the data\n"
-            "                inputs are merged into a single data tree where the expression\n"
-            "                is evaluated, so the -m option is always set implicitly.\n\n");
-
+            "                the operational datastore referenced from the operation.\n"
+            "                In case of a nested notification or action, its parent\n"
+            "                existence is also checked in these operational data.\n"
+            "  -R FILE, --reply-rpc=FILE\n"
+            "                Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n"
+            "                is supposed to contain the source 'nc-rpc' operation of the reply.\n");
+    cmd_data_help_format();
+    cmd_data_help_in_format();
+    printf("  -o OUTFILE, --output=OUTFILE\n"
+            "                Write the output to OUTFILE instead of stdout.\n");
+    cmd_data_help_xpath();
+    printf("\n");
 }
 
-void
-cmd_data(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
+    int rc = 0, argc = 0;
     int opt, opt_index;
     struct option options[] = {
         {"defaults",    required_argument, NULL, 'd'},
@@ -115,214 +162,525 @@
         {"merge",       no_argument,       NULL, 'm'},
         {"output",      required_argument, NULL, 'o'},
         {"operational", required_argument, NULL, 'O'},
+        {"reply-rpc",   required_argument, NULL, 'R'},
         {"not-strict",  no_argument,       NULL, 'n'},
         {"type",        required_argument, NULL, 't'},
         {"xpath",       required_argument, NULL, 'x'},
         {NULL, 0, NULL, 0}
     };
 
-    uint8_t data_merge = 0;
-    uint32_t options_print = 0;
-    uint32_t options_parse = YL_DEFAULT_DATA_PARSE_OPTIONS;
-    uint32_t options_validate = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
-    enum lyd_type data_type = 0;
     uint8_t data_type_set = 0;
-    LYD_FORMAT outformat = LYD_UNKNOWN;
-    LYD_FORMAT informat = LYD_UNKNOWN;
-    struct ly_out *out = NULL;
-    struct cmdline_file *operational = NULL;
-    struct ly_set inputs = {0};
-    struct ly_set xpaths = {0};
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
+    yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_DATA].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'd': /* --default */
-            if (!strcasecmp(optarg, "all")) {
-                options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
-            } else if (!strcasecmp(optarg, "all-tagged")) {
-                options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
-            } else if (!strcasecmp(optarg, "trim")) {
-                options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
-            } else if (!strcasecmp(optarg, "implicit-tagged")) {
-                options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
-            } else {
+            if (yo_opt_update_data_default(optarg, yo)) {
                 YLMSG_E("Unknown default mode %s\n", optarg);
-                cmd_data_help();
-                goto cleanup;
+                cmd_data_help_default();
+                return 1;
             }
             break;
         case 'f': /* --format */
-            if (!strcasecmp(optarg, "xml")) {
-                outformat = LYD_XML;
-            } else if (!strcasecmp(optarg, "json")) {
-                outformat = LYD_JSON;
-            } else if (!strcasecmp(optarg, "lyb")) {
-                outformat = LYD_LYB;
-            } else {
-                YLMSG_E("Unknown output format %s\n", optarg);
-                cmd_data_help();
-                goto cleanup;
+            if (yl_opt_update_data_out_format(optarg, yo)) {
+                cmd_data_help_format();
+                return 1;
             }
             break;
         case 'F': /* --in-format */
-            if (!strcasecmp(optarg, "xml")) {
-                informat = LYD_XML;
-            } else if (!strcasecmp(optarg, "json")) {
-                informat = LYD_JSON;
-            } else if (!strcasecmp(optarg, "lyb")) {
-                informat = LYD_LYB;
-            } else {
+            if (yo_opt_update_data_in_format(optarg, yo)) {
                 YLMSG_E("Unknown input format %s\n", optarg);
-                cmd_data_help();
-                goto cleanup;
+                cmd_data_help_in_format();
+                return 1;
             }
             break;
         case 'o': /* --output */
-            if (out) {
+            if (yo->out) {
                 YLMSG_E("Only a single output can be specified.\n");
-                goto cleanup;
+                return 1;
             } else {
-                if (ly_out_new_filepath(optarg, &out)) {
+                if (ly_out_new_filepath(optarg, &yo->out)) {
                     YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
-                    goto cleanup;
+                    return 1;
                 }
             }
             break;
-        case 'O': { /* --operational */
-            struct ly_in *in;
-            LYD_FORMAT f;
-
-            if (operational) {
+        case 'O':   /* --operational */
+            if (yo->data_operational.path) {
                 YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n");
-                goto cleanup;
+                return 1;
             }
-            if (get_input(optarg, NULL, &f, &in)) {
-                goto cleanup;
-            }
-            operational = fill_cmdline_file(NULL, in, optarg, f);
+            yo->data_operational.path = optarg;
             break;
-        } /* case 'O' */
-
+        case 'R':   /* --reply-rpc */
+            if (yo->reply_rpc.path) {
+                YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n");
+                return 1;
+            }
+            yo->reply_rpc.path = optarg;
+            break;
         case 'e': /* --present */
-            options_validate |= LYD_VALIDATE_PRESENT;
+            yo->data_validate_options |= LYD_VALIDATE_PRESENT;
             break;
         case 'm': /* --merge */
-            data_merge = 1;
+            yo->data_merge = 1;
             break;
         case 'n': /* --not-strict */
-            options_parse &= ~LYD_PARSE_STRICT;
+            yo->data_parse_options &= ~LYD_PARSE_STRICT;
             break;
         case 't': /* --type */
             if (data_type_set) {
                 YLMSG_E("The data type (-t) cannot be set multiple times.\n");
-                goto cleanup;
+                return 1;
             }
 
-            if (!strcasecmp(optarg, "config")) {
-                options_parse |= LYD_PARSE_NO_STATE;
-                options_validate |= LYD_VALIDATE_NO_STATE;
-            } else if (!strcasecmp(optarg, "get")) {
-                options_parse |= LYD_PARSE_ONLY;
-            } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config") || !strcasecmp(optarg, "edit")) {
-                options_parse |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
-            } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) {
-                data_type = LYD_TYPE_RPC_YANG;
-            } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) {
-                data_type = LYD_TYPE_REPLY_YANG;
-            } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) {
-                data_type = LYD_TYPE_NOTIF_YANG;
-            } else if (!strcasecmp(optarg, "data")) {
-                /* default option */
-            } else {
+            if (yl_opt_update_data_type(optarg, yo)) {
                 YLMSG_E("Unknown data tree type %s.\n", optarg);
-                cmd_data_help();
-                goto cleanup;
+                cmd_data_help_type();
+                return 1;
             }
 
             data_type_set = 1;
             break;
 
         case 'x': /* --xpath */
-            if (ly_set_add(&xpaths, optarg, 0, NULL)) {
+            if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) {
                 YLMSG_E("Storing XPath \"%s\" failed.\n", optarg);
-                goto cleanup;
+                return 1;
             }
             break;
 
         case 'h': /* --help */
             cmd_data_help();
-            goto cleanup;
+            return 1;
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (optind == argc) {
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return rc;
+}
+
+int
+cmd_data_dep(struct yl_opt *yo, int posc)
+{
+    if (yo->interactive && !posc) {
         YLMSG_E("Missing the data file to process.\n");
-        goto cleanup;
+        return 1;
     }
 
-    if (data_merge) {
-        if (data_type || (options_parse & LYD_PARSE_ONLY)) {
+    if (yo->data_merge) {
+        if (yo->data_type || (yo->data_parse_options & LYD_PARSE_ONLY)) {
             /* switch off the option, incompatible input data type */
-            data_merge = 0;
+            YLMSG_W("The --merge option has effect only for 'data' and 'config' TYPEs\n");
+            yo->data_merge = 0;
         } else {
             /* postpone validation after the merge of all the input data */
-            options_parse |= LYD_PARSE_ONLY;
+            yo->data_parse_options |= LYD_PARSE_ONLY;
         }
-    } else if (xpaths.count) {
-        data_merge = 1;
+    } else if (yo->data_xpath.count) {
+        yo->data_merge = 1;
     }
 
-    if (xpaths.count && outformat) {
+    if (yo->data_xpath.count && (yo->schema_out_format || yo->data_out_format)) {
         YLMSG_E("The --format option cannot be combined with --xpath option.\n");
-        cmd_data_help();
-        goto cleanup;
+        if (yo->interactive) {
+            cmd_data_help_xpath();
+        }
+        return 1;
+    }
+    if (yo->data_xpath.count && (yo->data_print_options & LYD_PRINT_WD_MASK)) {
+        YLMSG_E("The --default option cannot be combined with --xpath option.\n");
+        if (yo->interactive) {
+            cmd_data_help_xpath();
+        }
+        return 1;
     }
 
-    if (operational && !data_type) {
-        YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notifications input data types.\n");
-        free_cmdline_file(operational);
-        operational = NULL;
+    if (yo->data_operational.path && !yo->data_type) {
+        YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n");
+        yo->data_operational.path = NULL;
     }
 
-    /* process input data files provided as standalone command line arguments */
-    for (int i = 0; i < argc - optind; i++) {
-        struct ly_in *in;
+    if (yo->reply_rpc.path && (yo->data_type != LYD_TYPE_REPLY_NETCONF)) {
+        YLMSG_W("Source RPC is needed only for NETCONF Reply input data type.\n");
+        yo->data_operational.path = NULL;
+    } else if (!yo->reply_rpc.path && (yo->data_type == LYD_TYPE_REPLY_NETCONF)) {
+        YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n");
+        return 1;
+    }
 
-        if (get_input(argv[optind + i], NULL, &informat, &in)) {
-            goto cleanup;
-        }
-
-        if (!fill_cmdline_file(&inputs, in, argv[optind + i], informat)) {
-            ly_in_free(in, 1);
-            goto cleanup;
-        }
+    if (!yo->out && (yo->data_out_format == LYD_LYB)) {
+        YLMSG_E("The LYB format requires the -o option specified.\n");
+        return 1;
     }
 
     /* default output stream */
-    if (!out) {
-        if (ly_out_new_file(stdout, &out)) {
+    if (!yo->out) {
+        if (ly_out_new_file(stdout, &yo->out)) {
             YLMSG_E("Unable to set stdout as output.\n");
-            goto cleanup;
+            return 1;
+        }
+        yo->out_stdout = 1;
+    }
+
+    /* process the operational and/or reply RPC content if any */
+    if (yo->data_operational.path) {
+        if (get_input(yo->data_operational.path, NULL, &yo->data_operational.format, &yo->data_operational.in)) {
+            return -1;
+        }
+    }
+    if (yo->reply_rpc.path) {
+        if (get_input(yo->reply_rpc.path, NULL, &yo->reply_rpc.format, &yo->reply_rpc.in)) {
+            return -1;
         }
     }
 
-    /* parse, validate and print data */
-    if (process_data(*ctx, data_type, data_merge, outformat, out, options_parse, options_validate, options_print,
-            operational, NULL, &inputs, &xpaths)) {
+    return 0;
+}
+
+int
+cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    (void) ctx;
+    struct ly_in *in;
+    LYD_FORMAT format_data;
+
+    assert(posv);
+
+    format_data = yo->data_in_format;
+
+    /* process input data files provided as standalone command line arguments */
+    if (get_input(posv, NULL, &format_data, &in)) {
+        return 1;
+    }
+
+    if (!fill_cmdline_file(&yo->data_inputs, in, posv, format_data)) {
+        ly_in_free(in, 1);
+        return 1;
+    }
+
+    return 0;
+}
+
+/**
+ * @brief Evaluate xpath adn print result.
+ *
+ * @param[in] tree Data tree.
+ * @param[in] xpath Xpath to evaluate.
+ * @return 0 on success.
+ */
+static int
+evaluate_xpath(const struct lyd_node *tree, const char *xpath)
+{
+    struct ly_set *set = NULL;
+
+    if (lyd_find_xpath(tree, xpath, &set)) {
+        return -1;
+    }
+
+    /* print result */
+    printf("XPath \"%s\" evaluation result:\n", xpath);
+    if (!set->count) {
+        printf("\tEmpty\n");
+    } else {
+        for (uint32_t u = 0; u < set->count; ++u) {
+            struct lyd_node *node = (struct lyd_node *)set->objs[u];
+
+            printf("  %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
+            if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
+                printf(" (value: \"%s\")\n", lyd_get_value(node));
+            } else if (node->schema->nodetype == LYS_LIST) {
+                printf(" (");
+                for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
+                    printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
+                            key->schema->name, lyd_get_value(key));
+                }
+                printf(")\n");
+            } else {
+                printf("\n");
+            }
+        }
+    }
+
+    ly_set_free(set, NULL);
+    return 0;
+}
+
+/**
+ * @brief Checking that a parent data node exists in the datastore for the nested-notification and action.
+ *
+ * @param[in] op Operation to check.
+ * @param[in] oper_tree Data from datastore.
+ * @param[in] operational_f Operational datastore file information.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+check_operation_parent(struct lyd_node *op, struct lyd_node *oper_tree, struct cmdline_file *operational_f)
+{
+    LY_ERR ret;
+    struct ly_set *set = NULL;
+    char *path = NULL;
+
+    if (!op || !lyd_parent(op)) {
+        /* The function is defined only for nested-notification and action. */
+        return LY_SUCCESS;
+    }
+
+    if (!operational_f || (operational_f && !operational_f->in)) {
+        YLMSG_E("The --operational parameter needed to validate operation \"%s\" is missing.\n", LYD_NAME(op));
+        ret = LY_EVALID;
+        goto cleanup;
+    }
+
+    path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0);
+    if (!path) {
+        ret = LY_EMEM;
+        goto cleanup;
+    }
+
+    if (!oper_tree) {
+        YLMSG_W("Operational datastore is empty or contains unknown data.\n");
+        YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
+        ret = LY_EVALID;
+        goto cleanup;
+    }
+    if ((ret = lyd_find_xpath(oper_tree, path, &set))) {
+        goto cleanup;
+    }
+    if (!set->count) {
+        YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
+        ret = LY_EVALID;
         goto cleanup;
     }
 
 cleanup:
-    ly_out_free(out, NULL, 0);
-    ly_set_erase(&inputs, free_cmdline_file);
-    ly_set_erase(&xpaths, NULL);
-    free_cmdline_file(operational);
-    free_cmdline(argv);
+    ly_set_free(set, NULL);
+    free(path);
+
+    return ret;
+}
+
+/**
+ * @brief Process the input data files - parse, validate and print according to provided options.
+ *
+ * @param[in] ctx libyang context with schema.
+ * @param[in] type The type of data in the input files.
+ * @param[in] merge Flag if the data should be merged before validation.
+ * @param[in] out_format Data format for printing.
+ * @param[in] out The output handler for printing.
+ * @param[in] parse_options Parser options.
+ * @param[in] validate_options Validation options.
+ * @param[in] print_options Printer options.
+ * @param[in] operational Optional operational datastore file information for the case of an extended validation of
+ * operation(s).
+ * @param[in] reply_rpc Source RPC operation file information for parsing NETCONF rpc-reply.
+ * @param[in] inputs Set of file informations of input data files.
+ * @param[in] xpaths The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set
+ * is printed. Alternative to data printing.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT out_format,
+        struct ly_out *out, uint32_t parse_options, uint32_t validate_options, uint32_t print_options,
+        struct cmdline_file *operational, struct cmdline_file *reply_rpc, struct ly_set *inputs,
+        struct ly_set *xpaths)
+{
+    LY_ERR ret = LY_SUCCESS;
+    struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL;
+    const char *xpath;
+    struct ly_set *set = NULL;
+
+    /* additional operational datastore */
+    if (operational && operational->in) {
+        ret = lyd_parse_data(ctx, NULL, operational->in, operational->format, LYD_PARSE_ONLY, 0, &oper_tree);
+        if (ret) {
+            YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational->path);
+            goto cleanup;
+        }
+    }
+
+    for (uint32_t u = 0; u < inputs->count; ++u) {
+        struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
+
+        switch (type) {
+        case LYD_TYPE_DATA_YANG:
+            ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, &tree);
+            break;
+        case LYD_TYPE_RPC_YANG:
+        case LYD_TYPE_REPLY_YANG:
+        case LYD_TYPE_NOTIF_YANG:
+            ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &tree, &op);
+            break;
+        case LYD_TYPE_RPC_NETCONF:
+        case LYD_TYPE_NOTIF_NETCONF:
+            ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &envp, &op);
+
+            /* adjust pointers */
+            for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
+            break;
+        case LYD_TYPE_REPLY_NETCONF:
+            /* parse source RPC operation */
+            assert(reply_rpc && reply_rpc->in);
+            ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, &op);
+            if (ret) {
+                YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", reply_rpc->path);
+                goto cleanup;
+            }
+
+            /* adjust pointers */
+            for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
+
+            /* free input */
+            lyd_free_siblings(lyd_child(op));
+
+            /* we do not care */
+            lyd_free_all(envp);
+            envp = NULL;
+
+            ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, type, &envp, NULL);
+            break;
+        default:
+            YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
+            goto cleanup;
+        }
+
+        if (ret) {
+            YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
+            goto cleanup;
+        }
+
+        if (merge) {
+            /* merge the data so far parsed for later validation and print */
+            if (!merged_tree) {
+                merged_tree = tree;
+            } else {
+                ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
+                if (ret) {
+                    YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
+                    goto cleanup;
+                }
+            }
+            tree = NULL;
+        } else if (out_format) {
+            /* print */
+            switch (type) {
+            case LYD_TYPE_DATA_YANG:
+                lyd_print_all(out, tree, out_format, print_options);
+                break;
+            case LYD_TYPE_RPC_YANG:
+            case LYD_TYPE_REPLY_YANG:
+            case LYD_TYPE_NOTIF_YANG:
+            case LYD_TYPE_RPC_NETCONF:
+            case LYD_TYPE_NOTIF_NETCONF:
+                lyd_print_tree(out, tree, out_format, print_options);
+                break;
+            case LYD_TYPE_REPLY_NETCONF:
+                /* just the output */
+                lyd_print_tree(out, lyd_child(tree), out_format, print_options);
+                break;
+            default:
+                assert(0);
+            }
+        } else {
+            /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */
+            switch (type) {
+            case LYD_TYPE_DATA_YANG:
+                /* already validated */
+                break;
+            case LYD_TYPE_RPC_YANG:
+            case LYD_TYPE_RPC_NETCONF:
+                ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL);
+                break;
+            case LYD_TYPE_REPLY_YANG:
+            case LYD_TYPE_REPLY_NETCONF:
+                ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL);
+                break;
+            case LYD_TYPE_NOTIF_YANG:
+            case LYD_TYPE_NOTIF_NETCONF:
+                ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL);
+                break;
+            default:
+                assert(0);
+            }
+            if (ret) {
+                if (operational->path) {
+                    YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
+                            input_f->path, operational->path);
+                } else {
+                    YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path);
+                }
+                goto cleanup;
+            }
+
+            if ((ret = check_operation_parent(op, oper_tree, operational))) {
+                goto cleanup;
+            }
+        }
+
+        /* next iter */
+        lyd_free_all(tree);
+        tree = NULL;
+        lyd_free_all(envp);
+        envp = NULL;
+    }
+
+    if (merge) {
+        /* validate the merged result */
+        ret = lyd_validate_all(&merged_tree, ctx, validate_options, NULL);
+        if (ret) {
+            YLMSG_E("Merged data are not valid.\n");
+            goto cleanup;
+        }
+
+        if (out_format) {
+            /* and print it */
+            lyd_print_all(out, merged_tree, out_format, print_options);
+        }
+
+        for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) {
+            xpath = (const char *)xpaths->objs[u];
+            ly_set_free(set, NULL);
+            ret = lys_find_xpath(ctx, NULL, xpath, LYS_FIND_NO_MATCH_ERROR, &set);
+            if (ret || !set->count) {
+                ret = (ret == LY_SUCCESS) ? LY_EINVAL : ret;
+                YLMSG_E("The requested xpath failed.\n");
+                goto cleanup;
+            }
+            if (evaluate_xpath(merged_tree, xpath)) {
+                goto cleanup;
+            }
+        }
+    }
+
+cleanup:
+    lyd_free_all(tree);
+    lyd_free_all(envp);
+    lyd_free_all(merged_tree);
+    lyd_free_all(oper_tree);
+    ly_set_free(set, NULL);
+    return ret;
+}
+
+int
+cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo)
+{
+    /* parse, validate and print data */
+    if (process_data(ctx, yo->data_type, yo->data_merge, yo->data_out_format, yo->out, yo->data_parse_options,
+            yo->data_validate_options, yo->data_print_options, &yo->data_operational, &yo->reply_rpc,
+            &yo->data_inputs, &yo->data_xpath)) {
+        return 1;
+    }
+
+    return 0;
 }
diff --git a/tools/lint/cmd_debug.c b/tools/lint/cmd_debug.c
new file mode 100644
index 0000000..1432fe9
--- /dev/null
+++ b/tools/lint/cmd_debug.c
@@ -0,0 +1,134 @@
+/**
+ * @file cmd_debug.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'verb' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2023-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef NDEBUG
+
+#include "cmd.h"
+
+#include <assert.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+struct debug_groups {
+    char *name;
+    uint32_t flag;
+} const dg [] = {
+    {"dict", LY_LDGDICT},
+    {"xpath", LY_LDGXPATH},
+    {"dep-sets", LY_LDGDEPSETS},
+};
+#define DG_LENGTH (sizeof dg / sizeof *dg)
+
+void
+cmd_debug_help(void)
+{
+    uint32_t i;
+
+    printf("Usage: debug (");
+    for (i = 0; i < DG_LENGTH; i++) {
+        if ((i + 1) == DG_LENGTH) {
+            printf("%s", dg[i].name);
+        } else {
+            printf("%s | ", dg[i].name);
+        }
+    }
+    printf(")+\n");
+}
+
+int
+cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+    int rc = 0, argc = 0;
+    int opt, opt_index;
+    struct option options[] = {
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
+    }
+
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) {
+        switch (opt) {
+        case 'h':
+            cmd_debug_help();
+            return 1;
+        default:
+            YLMSG_E("Unknown option.\n");
+            return 1;
+        }
+    }
+
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_debug_dep(struct yl_opt *yo, int posc)
+{
+    (void) yo;
+
+    if (yo->interactive && !posc) {
+        /* no argument */
+        cmd_debug_help();
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    (void) ctx;
+    uint32_t i;
+    ly_bool set;
+
+    assert(posv);
+
+    set = 0;
+    for (i = 0; i < DG_LENGTH; i++) {
+        if (!strcasecmp(posv, dg[i].name)) {
+            yo->dbg_groups |= dg[i].flag;
+            set = 1;
+            break;
+        }
+    }
+
+    if (!set) {
+        YLMSG_E("Unknown debug group \"%s\"\n", posv);
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo)
+{
+    (void) ctx;
+    return ly_log_dbg_groups(yo->dbg_groups);
+}
+
+#endif
diff --git a/tools/lint/cmd_extdata.c b/tools/lint/cmd_extdata.c
new file mode 100644
index 0000000..fe14f4a
--- /dev/null
+++ b/tools/lint/cmd_extdata.c
@@ -0,0 +1,115 @@
+/**
+ * @file cmd_extdata.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'extdata' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200809L /* strdup */
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+char *filename;
+
+void
+cmd_extdata_free(void)
+{
+    free(filename);
+    filename = NULL;
+}
+
+void
+cmd_extdata_help(void)
+{
+    printf("Usage: extdata [--clear] [<extdata-file-path>]\n"
+            "                 File containing the specific data required by an extension. Required by\n"
+            "                 the schema-mount extension, for example, when the operational data are\n"
+            "                 expected in the file. File format is guessed.\n");
+}
+
+int
+cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+    int rc = 0, argc = 0;
+    int opt, opt_index;
+    struct option options[] = {
+        {"clear", no_argument, NULL, 'c'},
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
+    }
+
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_EXTDATA].optstring, options, &opt_index)) != -1) {
+        switch (opt) {
+        case 'c':
+            yo->extdata_unset = 1;
+            free(filename);
+            filename = NULL;
+            break;
+        case 'h':
+            cmd_extdata_help();
+            return 1;
+        default:
+            YLMSG_E("Unknown option.\n");
+            return 1;
+        }
+    }
+
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_extdata_dep(struct yl_opt *yo, int posc)
+{
+    if (!yo->extdata_unset && (posc > 1)) {
+        YLMSG_E("Only one file must be entered.\n");
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    if (yo->extdata_unset) {
+        ly_ctx_set_ext_data_clb(*ctx, NULL, NULL);
+    } else if (!yo->extdata_unset && !posv) {
+        /* no argument - print the current file */
+        printf("%s\n", filename ? filename : "No file set.");
+    } else if (posv) {
+        /* set callback providing run-time extension instance data */
+        free(filename);
+        filename = strdup(posv);
+        if (!filename) {
+            YLMSG_E("Memory allocation error.\n");
+            return 1;
+        }
+        ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, filename);
+    }
+
+    return 0;
+}
diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c
index 6b332ab..5c8bd82 100644
--- a/tools/lint/cmd_feature.c
+++ b/tools/lint/cmd_feature.c
@@ -1,9 +1,10 @@
 /**
  * @file cmd_feature.c
  * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'feature' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2021 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -23,6 +24,8 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
 
 void
 cmd_feature_help(void)
@@ -37,17 +40,11 @@
             "                  Print features of all implemented modules.\n");
 }
 
-void
-cmd_feature(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
-    char *features_output = NULL;
-    int opt, opt_index, i;
-    ly_bool generate_features = 0, print_all = 0;
-    struct ly_set set = {0};
-    const struct lys_module *mod;
-    struct ly_out *out = NULL;
+    int rc = 0, argc = 0;
+    int opt, opt_index;
     struct option options[] = {
         {"help", no_argument, NULL, 'h'},
         {"all", no_argument, NULL, 'a'},
@@ -55,81 +52,80 @@
         {NULL, 0, NULL, 0}
     };
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'h':
             cmd_feature_help();
-            goto cleanup;
+            return 1;
         case 'a':
-            print_all = 1;
+            yo->feature_print_all = 1;
             break;
         case 'f':
-            generate_features = 1;
+            yo->feature_param_format = 1;
             break;
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (ly_out_new_file(stdout, &out)) {
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_feature_dep(struct yl_opt *yo, int posc)
+{
+    if (ly_out_new_file(stdout, &yo->out)) {
         YLMSG_E("Unable to print to the standard output.\n");
-        goto cleanup;
+        return 1;
     }
+    yo->out_stdout = 1;
 
-    if (print_all) {
-        if (print_all_features(out, *ctx, generate_features, &features_output)) {
-            YLMSG_E("Printing all features failed.\n");
-            goto cleanup;
-        }
-        if (generate_features) {
-            printf("%s\n", features_output);
-        }
-        goto cleanup;
-    }
-
-    if (argc == optind) {
+    if (yo->interactive && !yo->feature_print_all && !posc) {
         YLMSG_E("Missing modules to print.\n");
-        goto cleanup;
+        return 1;
     }
 
-    for (i = 0; i < argc - optind; i++) {
-        /* always erase the set, so the previous module's features don't carry over to the next module's features */
-        ly_set_erase(&set, NULL);
+    return 0;
+}
 
-        mod = ly_ctx_get_module_latest(*ctx, argv[optind + i]);
-        if (!mod) {
-            YLMSG_E("Module \"%s\" not found.\n", argv[optind + i]);
-            goto cleanup;
-        }
+int
+cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    const struct lys_module *mod;
 
-        /* collect features of the module */
-        if (collect_features(mod, &set)) {
-            goto cleanup;
-        }
-
-        if (generate_features) {
-            if (generate_features_output(mod, &set, &features_output)) {
-                goto cleanup;
-            }
-            /* don't print features and their state of each module if generating features parameter */
-            continue;
-        }
-
-        print_features(out, mod, &set);
+    if (yo->feature_print_all) {
+        print_all_features(yo->out, *ctx, yo->feature_param_format);
+        return 0;
     }
 
-    if (generate_features) {
-        printf("%s\n", features_output);
+    mod = ly_ctx_get_module_latest(*ctx, posv);
+    if (!mod) {
+        YLMSG_E("Module \"%s\" not found.\n", posv);
+        return 1;
     }
 
-cleanup:
-    free_cmdline(argv);
-    ly_out_free(out, NULL, 0);
-    ly_set_erase(&set, NULL);
-    free(features_output);
+    if (yo->feature_param_format) {
+        print_feature_param(yo->out, mod);
+    } else {
+        print_features(yo->out, mod);
+    }
+
+    return 0;
+}
+
+int
+cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo)
+{
+    (void) ctx;
+
+    ly_print(yo->out, "\n");
+    return 0;
 }
diff --git a/tools/lint/cmd_help.c b/tools/lint/cmd_help.c
new file mode 100644
index 0000000..8a2a597
--- /dev/null
+++ b/tools/lint/cmd_help.c
@@ -0,0 +1,107 @@
+/**
+ * @file cmd_help.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'help' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2023-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+void
+cmd_help_help(void)
+{
+    printf("Usage: help [cmd ...]\n");
+}
+
+int
+cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+    int rc = 0, argc = 0;
+    int opt, opt_index;
+    struct option options[] = {
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
+    }
+
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) {
+        if (opt == 'h') {
+            cmd_help_help();
+            return 1;
+        } else {
+            YLMSG_E("Unknown option.\n");
+            return 1;
+        }
+    }
+
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return rc;
+}
+
+static void
+print_generic_help(void)
+{
+    printf("Available commands:\n");
+    for (uint16_t i = 0; commands[i].name; i++) {
+        if (commands[i].helpstring != NULL) {
+            printf("  %-15s %s\n", commands[i].name, commands[i].helpstring);
+        }
+    }
+}
+
+int
+cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    (void)ctx, (void)yo;
+
+    if (!posv) {
+        print_generic_help();
+    } else {
+        /* print specific help for the selected command(s) */
+
+        int8_t match = 0;
+
+        /* get the command of the specified name */
+        for (uint16_t i = 0; commands[i].name; i++) {
+            if (strcmp(posv, commands[i].name) == 0) {
+                match = 1;
+                if (commands[i].help_func != NULL) {
+                    commands[i].help_func();
+                } else {
+                    printf("%s: %s\n", posv, commands[i].helpstring);
+                }
+                break;
+            }
+        }
+        if (!match) {
+            /* if unknown command specified, print the list of commands */
+            printf("Unknown command \'%s\'\n", posv);
+            print_generic_help();
+        }
+    }
+
+    return 0;
+}
diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c
index ec7a021..fb9a2a7 100644
--- a/tools/lint/cmd_list.c
+++ b/tools/lint/cmd_list.c
@@ -2,9 +2,10 @@
  * @file cmd_list.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'list' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
 
 void
 cmd_list_help(void)
@@ -37,53 +39,147 @@
             "                  modules.\n");
 }
 
-void
-cmd_list(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
+    int rc = 0, argc = 0;
     int opt, opt_index;
     struct option options[] = {
         {"format", required_argument, NULL, 'f'},
         {"help", no_argument, NULL, 'h'},
         {NULL, 0, NULL, 0}
     };
-    LYD_FORMAT format = LYD_UNKNOWN;
-    struct ly_out *out = NULL;
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    yo->data_out_format = LYD_UNKNOWN;
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'f': /* --format */
             if (!strcasecmp(optarg, "xml")) {
-                format = LYD_XML;
+                yo->data_out_format = LYD_XML;
             } else if (!strcasecmp(optarg, "json")) {
-                format = LYD_JSON;
+                yo->data_out_format = LYD_JSON;
             } else {
                 YLMSG_E("Unknown output format %s\n", optarg);
                 cmd_list_help();
-                goto cleanup;
+                return 1;
             }
             break;
         case 'h':
             cmd_list_help();
-            goto cleanup;
+            return 1;
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (!ly_out_new_file(stdout, &out)) {
-        print_list(out, *ctx, format);
-        ly_out_free(out, NULL,  0);
-    } else {
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_list_dep(struct yl_opt *yo, int posc)
+{
+    if (posc) {
+        YLMSG_E("No positional arguments are allowed.\n");
+        return 1;
+    }
+    if (ly_out_new_file(stdout, &yo->out)) {
         YLMSG_E("Unable to print to the standard output.\n");
+        return 1;
+    }
+    yo->out_stdout = 1;
+
+    return 0;
+}
+
+/**
+ * @brief Print yang library data.
+ *
+ * @param[in] ctx Context for libyang.
+ * @param[in] data_out_format Output format of printed data.
+ * @param[in] out Output handler.
+ * @return 0 on success.
+ */
+static int
+print_yang_lib_data(struct ly_ctx *ctx, LYD_FORMAT data_out_format, struct ly_out *out)
+{
+    struct lyd_node *ylib;
+
+    if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
+        YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n");
+        return 1;
     }
 
-cleanup:
-    free_cmdline(argv);
+    lyd_print_all(out, ylib, data_out_format, 0);
+    lyd_free_all(ylib);
+
+    return 0;
+}
+
+int
+cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    (void) posv;
+    uint32_t idx = 0, has_modules = 0;
+    const struct lys_module *mod;
+
+    if (yo->data_out_format != LYD_UNKNOWN) {
+        /* ietf-yang-library data are printed in the specified format */
+        if (print_yang_lib_data(*ctx, yo->data_out_format, yo->out)) {
+            return 1;
+        }
+        return 0;
+    }
+
+    /* iterate schemas in context and provide just the basic info */
+    ly_print(yo->out, "List of the loaded models:\n");
+    while ((mod = ly_ctx_get_module_iter(*ctx, &idx))) {
+        has_modules++;
+
+        /* conformance print */
+        if (mod->implemented) {
+            ly_print(yo->out, "    I");
+        } else {
+            ly_print(yo->out, "    i");
+        }
+
+        /* module print */
+        ly_print(yo->out, " %s", mod->name);
+        if (mod->revision) {
+            ly_print(yo->out, "@%s", mod->revision);
+        }
+
+        /* submodules print */
+        if (mod->parsed && mod->parsed->includes) {
+            uint64_t u = 0;
+
+            ly_print(yo->out, " (");
+            LY_ARRAY_FOR(mod->parsed->includes, u) {
+                ly_print(yo->out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
+                if (mod->parsed->includes[u].rev[0]) {
+                    ly_print(yo->out, "@%s", mod->parsed->includes[u].rev);
+                }
+            }
+            ly_print(yo->out, ")");
+        }
+
+        /* finish the line */
+        ly_print(yo->out, "\n");
+    }
+
+    if (!has_modules) {
+        ly_print(yo->out, "\t(none)\n");
+    }
+
+    ly_print_flush(yo->out);
+
+    return 0;
 }
diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c
index f5883e9..f7457c8 100644
--- a/tools/lint/cmd_load.c
+++ b/tools/lint/cmd_load.c
@@ -2,9 +2,10 @@
  * @file cmd_load.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'load' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@
 
 #include "cmd.h"
 
+#include <assert.h>
 #include <getopt.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -25,13 +27,15 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
 
 void
 cmd_load_help(void)
 {
     printf("Usage: load [-i] <module-name1>[@<revision>] [<module-name2>[@revision] ...]\n"
             "                  Add a new module of the specified name, yanglint will find\n"
-            "                  them in searchpaths. if the <revision> of the module not\n"
+            "                  them in searchpaths. If the <revision> of the module not\n"
             "                  specified, the latest revision available is loaded.\n\n"
             "  -F FEATURES, --features=FEATURES\n"
             "                  Features to support, default all in all implemented modules.\n"
@@ -39,101 +43,110 @@
             "  -i, --make-implemented\n"
             "                  Make the imported modules \"referenced\" from any loaded\n"
             "                  <schema> module also implemented. If specified a second time,\n"
-            "                  all the modules are set implemented.\n");
+            "                  all the modules are set implemented.\n"
+            "  -X, --extended-leafref\n"
+            "                  Allow usage of deref() XPath function within leafref.\n");
 }
 
-void
-cmd_load(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
+    int rc, argc = 0;
     int opt, opt_index;
     struct option options[] = {
         {"features", required_argument, NULL, 'F'},
         {"help", no_argument, NULL, 'h'},
         {"make-implemented", no_argument, NULL, 'i'},
+        {"extended-leafref", no_argument, NULL, 'X'},
         {NULL, 0, NULL, 0}
     };
-    uint16_t options_ctx = 0;
-    const char *all_features[] = {"*", NULL};
-    struct ly_set fset = {0};
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'F': /* --features */
-            if (parse_features(optarg, &fset)) {
-                goto cleanup;
+            if (parse_features(optarg, &yo->schema_features)) {
+                return 1;
             }
             break;
 
         case 'h':
             cmd_load_help();
-            goto cleanup;
+            return 1;
 
         case 'i': /* --make-implemented */
-            if (options_ctx & LY_CTX_REF_IMPLEMENTED) {
-                options_ctx &= ~LY_CTX_REF_IMPLEMENTED;
-                options_ctx |= LY_CTX_ALL_IMPLEMENTED;
-            } else {
-                options_ctx |= LY_CTX_REF_IMPLEMENTED;
-            }
+            yo_opt_update_make_implemented(yo);
+            break;
+
+        case 'X': /* --extended-leafref */
+            yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
             break;
 
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (argc == optind) {
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_load_dep(struct yl_opt *yo, int posc)
+{
+    if (yo->interactive && !posc) {
         /* no argument */
-        cmd_add_help();
-        goto cleanup;
+        cmd_load_help();
+        return 1;
     }
 
-    if (!fset.count) {
+    if (!yo->schema_features.count) {
         /* no features, enable all of them */
-        options_ctx |= LY_CTX_ENABLE_IMP_FEATURES;
+        yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES;
     }
 
-    if (options_ctx) {
-        ly_ctx_set_options(*ctx, options_ctx);
+    return 0;
+}
+
+int
+cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    const char *all_features[] = {"*", NULL};
+    char *revision;
+    const char **features = NULL;
+
+    assert(posv);
+
+    if (yo->ctx_options) {
+        ly_ctx_set_options(*ctx, yo->ctx_options);
+        yo->ctx_options = 0;
     }
 
-    for (int i = 0; i < argc - optind; i++) {
-        /* process the schema module files */
-        char *revision;
-        const char **features = NULL;
-
-        /* get revision */
-        revision = strchr(argv[optind + i], '@');
-        if (revision) {
-            revision[0] = '\0';
-            ++revision;
-        }
-
-        /* get features list for this module */
-        if (!fset.count) {
-            features = all_features;
-        } else {
-            get_features(&fset, argv[optind + i], &features);
-        }
-
-        /* load the module */
-        if (!ly_ctx_load_module(*ctx, argv[optind + i], revision, features)) {
-            /* libyang printed the error messages */
-            goto cleanup;
-        }
+    /* get revision */
+    revision = strchr(posv, '@');
+    if (revision) {
+        revision[0] = '\0';
+        ++revision;
     }
 
-cleanup:
-    if (options_ctx) {
-        ly_ctx_unset_options(*ctx, options_ctx);
+    /* get features list for this module */
+    if (!yo->schema_features.count) {
+        features = all_features;
+    } else {
+        get_features(&yo->schema_features, posv, &features);
     }
-    ly_set_erase(&fset, free_features);
-    free_cmdline(argv);
+
+    /* load the module */
+    if (!ly_ctx_load_module(*ctx, posv, revision, features)) {
+        /* libyang printed the error messages */
+        return 1;
+    }
+
+    return 0;
 }
diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c
index c1a5359..a89ac1a 100644
--- a/tools/lint/cmd_print.c
+++ b/tools/lint/cmd_print.c
@@ -2,9 +2,10 @@
  * @file cmd_print.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'print' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -27,6 +28,7 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
 
 void
 cmd_print_help(void)
@@ -34,7 +36,8 @@
     printf("Usage: print [-f (yang | yin | tree [-q -P PATH -L LINE_LENGTH ] | info [-q -P PATH])]\n"
             "            [-o OUTFILE] [<module-name1>[@revision]] ...\n"
             "                  Print a schema module. The <module-name> is not required\n"
-            "                  only in case the -P option is specified.\n\n"
+            "                  only in case the -P option is specified. For yang, yin and tree\n"
+            "                  formats, a submodule can also be printed.\n\n"
             "  -f FORMAT, --format=FORMAT\n"
             "                  Print the module in the specified FORMAT. If format not\n"
             "                  specified, the 'tree' format is used.\n"
@@ -53,8 +56,105 @@
             "                  Write the output to OUTFILE instead of stdout.\n");
 }
 
+int
+cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+    int rc = 0, argc = 0;
+    int opt, opt_index;
+    struct option options[] = {
+        {"format", required_argument, NULL, 'f'},
+        {"help", no_argument, NULL, 'h'},
+        {"tree-line-length", required_argument, NULL, 'L'},
+        {"output", required_argument, NULL, 'o'},
+        {"schema-node", required_argument, NULL, 'P'},
+        {"single-node", no_argument, NULL, 'q'},
+        {NULL, 0, NULL, 0}
+    };
+
+    yo->schema_out_format = LYS_OUT_TREE;
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
+    }
+
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_PRINT].optstring, options, &opt_index)) != -1) {
+        switch (opt) {
+        case 'o': /* --output */
+            if (yo->out) {
+                YLMSG_E("Only a single output can be specified.\n");
+                return 1;
+            } else {
+                if (ly_out_new_filepath(optarg, &yo->out)) {
+                    YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
+                    return 1;
+                }
+            }
+            break;
+
+        case 'f': /* --format */
+            if (yl_opt_update_schema_out_format(optarg, yo)) {
+                cmd_print_help();
+                return 1;
+            }
+            break;
+
+        case 'L': /* --tree-line-length */
+            yo->line_length = atoi(optarg);
+            break;
+
+        case 'P': /* --schema-node */
+            yo->schema_node_path = optarg;
+            break;
+
+        case 'q': /* --single-node */
+            yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
+            break;
+
+        case 'h':
+            cmd_print_help();
+            return 1;
+        default:
+            YLMSG_E("Unknown option.\n");
+            return 1;
+        }
+    }
+
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_print_dep(struct yl_opt *yo, int posc)
+{
+    /* file name */
+    if (yo->interactive && !posc && !yo->schema_node_path) {
+        YLMSG_E("Missing the name of the module to print.\n");
+        return 1;
+    }
+
+    if ((yo->schema_out_format != LYS_OUT_TREE) && yo->line_length) {
+        YLMSG_W("--tree-line-length take effect only in case of the tree output format.\n");
+    }
+
+    if (!yo->out) {
+        if (ly_out_new_file(stdout, &yo->out)) {
+            YLMSG_E("Could not use stdout to print output.\n");
+        }
+        yo->out_stdout = 1;
+    }
+
+    if (yo->schema_out_format == LYS_OUT_TREE) {
+        /* print tree from lysc_nodes */
+        yo->ctx_options |= LY_CTX_SET_PRIV_PARSED;
+    }
+
+    return 0;
+}
+
 static LY_ERR
-cmd_print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
 {
     LY_ERR erc;
     const struct lysp_submodule *submodule;
@@ -67,11 +167,19 @@
             lys_print_submodule(out, submodule, format, line_length, options) :
             LY_ENOTFOUND;
 
+    if (!erc) {
+        return 0;
+    } else if ((erc == LY_ENOTFOUND) && revision) {
+        YLMSG_E("No submodule \"%s\" found.\n", name);
+    } else {
+        YLMSG_E("Unable to print submodule %s.\n", name);
+    }
+
     return erc;
 }
 
 static LY_ERR
-cmd_print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
 {
     LY_ERR erc;
     struct lys_module *module;
@@ -84,181 +192,108 @@
             lys_print_module(out, module, format, line_length, options) :
             LY_ENOTFOUND;
 
+    if (!erc) {
+        return 0;
+    } else if ((erc == LY_ENOTFOUND) && revision) {
+        YLMSG_E("No module \"%s\" found.\n", name);
+    } else {
+        YLMSG_E("Unable to print module %s.\n", name);
+    }
+
     return erc;
 }
 
-static void
-cmd_print_modules(int argc, char **argv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, size_t line_length, uint32_t options)
+static int
+cmd_print_module(const char *posv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format,
+        size_t line_length, uint32_t options)
 {
     LY_ERR erc;
-    char *name, *revision;
-    ly_bool search_submodul;
-    const int stop = argc - optind;
+    char *name = NULL, *revision;
 
-    for (int i = 0; i < stop; i++) {
-        name = argv[optind + i];
-        /* get revision */
-        revision = strchr(name, '@');
-        if (revision) {
-            revision[0] = '\0';
-            ++revision;
-        }
-
-        erc = cmd_print_module(out, ctx, name, revision, format, line_length, options);
-
-        if (erc == LY_ENOTFOUND) {
-            search_submodul = 1;
-            erc = cmd_print_submodule(out, ctx, name, revision, format, line_length, options);
-        } else {
-            search_submodul = 0;
-        }
-
-        if (erc == LY_SUCCESS) {
-            /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */
-            if ((format == LYS_OUT_TREE) && (i + 1 < stop)) {
-                ly_print(out, "\n");
-            }
-            continue;
-        } else if (erc == LY_ENOTFOUND) {
-            if (revision) {
-                YLMSG_E("No (sub)module \"%s\" in revision %s found.\n", name, revision);
-            } else {
-                YLMSG_E("No (sub)module \"%s\" found.\n", name);
-            }
-            break;
-        } else {
-            if (search_submodul) {
-                YLMSG_E("Unable to print submodule %s.\n", name);
-            } else {
-                YLMSG_E("Unable to print module %s.\n", name);
-            }
-            break;
-        }
+    name = strdup(posv);
+    /* get revision */
+    revision = strchr(name, '@');
+    if (revision) {
+        revision[0] = '\0';
+        ++revision;
     }
+
+    erc = print_module(out, ctx, name, revision, format, line_length, options);
+
+    if (erc == LY_ENOTFOUND) {
+        erc = print_submodule(out, ctx, name, revision, format, line_length, options);
+    }
+
+    free(name);
+    return erc;
 }
 
-void
-cmd_print(struct ly_ctx **ctx, const char *cmdline)
+/**
+ * @brief Print schema node path.
+ *
+ * @param[in] ctx Context for libyang.
+ * @param[in] yo Context for yanglint.
+ * @return 0 on success.
+ */
+static int
+print_node(struct ly_ctx *ctx, struct yl_opt *yo)
 {
-    int argc = 0;
-    char **argv = NULL;
-    int opt, opt_index;
-    struct option options[] = {
-        {"format", required_argument, NULL, 'f'},
-        {"help", no_argument, NULL, 'h'},
-        {"tree-line-length", required_argument, NULL, 'L'},
-        {"output", required_argument, NULL, 'o'},
-        {"schema-node", required_argument, NULL, 'P'},
-        {"single-node", no_argument, NULL, 'q'},
-        {NULL, 0, NULL, 0}
-    };
-    uint16_t options_print = 0;
-    const char *node_path = NULL;
-    LYS_OUTFORMAT format = LYS_OUT_TREE;
-    struct ly_out *out = NULL;
-    ly_bool out_stdout = 0;
-    size_t line_length = 0;
+    const struct lysc_node *node;
+    uint32_t temp_lo = 0;
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
-    }
-
-    while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) {
-        switch (opt) {
-        case 'o': /* --output */
-            if (out) {
-                if (ly_out_filepath(out, optarg) != NULL) {
-                    YLMSG_E("Unable to use output file %s for printing output.\n", optarg);
-                    goto cleanup;
-                }
-            } else {
-                if (ly_out_new_filepath(optarg, &out)) {
-                    YLMSG_E("Unable to use output file %s for printing output.\n", optarg);
-                    goto cleanup;
-                }
+    if (yo->interactive) {
+        /* Use the same approach as for completion. */
+        node = find_schema_path(ctx, yo->schema_node_path);
+        if (!node) {
+            YLMSG_E("The requested schema node \"%s\" does not exists.\n", yo->schema_node_path);
+            return 1;
+        }
+    } else {
+        /* turn off logging so that the message is not repeated */
+        ly_temp_log_options(&temp_lo);
+        /* search operation input */
+        node = lys_find_path(ctx, NULL, yo->schema_node_path, 0);
+        if (!node) {
+            /* restore logging so an error may be displayed */
+            ly_temp_log_options(NULL);
+            /* search operation output */
+            node = lys_find_path(ctx, NULL, yo->schema_node_path, 1);
+            if (!node) {
+                YLMSG_E("Invalid schema path.\n");
+                return 1;
             }
-            break;
-
-        case 'f': /* --format */
-            if (!strcasecmp(optarg, "yang")) {
-                format = LYS_OUT_YANG;
-            } else if (!strcasecmp(optarg, "yin")) {
-                format = LYS_OUT_YIN;
-            } else if (!strcasecmp(optarg, "info")) {
-                format = LYS_OUT_YANG_COMPILED;
-            } else if (!strcasecmp(optarg, "tree")) {
-                format = LYS_OUT_TREE;
-            } else {
-                YLMSG_E("Unknown output format %s\n", optarg);
-                cmd_print_help();
-                goto cleanup;
-            }
-            break;
-
-        case 'L': /* --tree-line-length */
-            line_length = atoi(optarg);
-            break;
-
-        case 'P': /* --schema-node */
-            node_path = optarg;
-            break;
-
-        case 'q': /* --single-node */
-            options_print |= LYS_PRINT_NO_SUBSTMT;
-            break;
-
-        case 'h':
-            cmd_print_help();
-            goto cleanup;
-        default:
-            YLMSG_E("Unknown option.\n");
-            goto cleanup;
         }
     }
 
-    /* file name */
-    if ((argc == optind) && !node_path) {
-        YLMSG_E("Missing the name of the module to print.\n");
-        goto cleanup;
+    if (lys_print_node(yo->out, node, yo->schema_out_format, yo->line_length, yo->schema_print_options)) {
+        YLMSG_E("Unable to print schema node %s.\n", yo->schema_node_path);
+        return 1;
     }
 
-    if ((format != LYS_OUT_TREE) && line_length) {
-        YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n");
-        goto cleanup;
-    }
+    return 0;
+}
 
-    if (!out) {
-        if (ly_out_new_file(stdout, &out)) {
-            YLMSG_E("Could not use stdout to print output.\n");
-            goto cleanup;
-        }
-        out_stdout = 1;
-    }
+int
+cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    int rc = 0;
 
-    if (format == LYS_OUT_TREE) {
+    if (yo->ctx_options & LY_CTX_SET_PRIV_PARSED) {
         /* print tree from lysc_nodes */
         ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED);
     }
 
-    if (node_path) {
-        const struct lysc_node *node;
-
-        node = find_schema_path(*ctx, node_path);
-        if (!node) {
-            YLMSG_E("The requested schema node \"%s\" does not exists.\n", node_path);
-            goto cleanup;
-        }
-
-        if (lys_print_node(out, node, format, 0, options_print)) {
-            YLMSG_E("Unable to print schema node %s.\n", node_path);
-            goto cleanup;
-        }
+    if (yo->schema_node_path) {
+        rc = print_node(*ctx, yo);
+    } else if (!yo->interactive && yo->submodule) {
+        rc = print_submodule(yo->out, ctx, yo->submodule, NULL, yo->schema_out_format, yo->line_length,
+                yo->schema_print_options);
     } else {
-        cmd_print_modules(argc, argv, out, ctx, format, line_length, options_print);
-        goto cleanup;
+        rc = cmd_print_module(posv, yo->out, ctx, yo->schema_out_format, yo->line_length, yo->schema_print_options);
+        if (!yo->last_one && (yo->schema_out_format == LYS_OUT_TREE)) {
+            ly_print(yo->out, "\n");
+        }
     }
 
-cleanup:
-    free_cmdline(argv);
-    ly_out_free(out, NULL, out_stdout ? 0 : 1);
+    return rc;
 }
diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c
index 529e05d..15c98a1 100644
--- a/tools/lint/cmd_searchpath.c
+++ b/tools/lint/cmd_searchpath.c
@@ -2,9 +2,10 @@
  * @file cmd_searchpath.c
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief 'searchpath' command of the libyang's yanglint tool.
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -24,51 +25,62 @@
 #include "libyang.h"
 
 #include "common.h"
+#include "yl_opt.h"
 
 void
 cmd_searchpath_help(void)
 {
     printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n"
-            "                  Set paths of directories where to search for imports and\n"
-            "                  includes of the schema modules. The current working directory\n"
-            "                  and the path of the module being added is used implicitly.\n"
-            "                  The 'load' command uses these paths to search even for the\n"
-            "                  schema modules to be loaded.\n");
+            "                  Set paths of directories where to search for imports and includes\n"
+            "                  of the schema modules. Subdirectories are also searched. The current\n"
+            "                  working directory and the path of the module being added is used implicitly.\n"
+            "                  The 'load' command uses these paths to search even for the schema modules\n"
+            "                  to be loaded.\n");
 }
 
-void
-cmd_searchpath(struct ly_ctx **ctx, const char *cmdline)
+int
+cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
 {
-    int argc = 0;
-    char **argv = NULL;
+    int rc = 0, argc = 0;
     int opt, opt_index;
     struct option options[] = {
         {"clear", no_argument, NULL, 'c'},
         {"help", no_argument, NULL, 'h'},
         {NULL, 0, NULL, 0}
     };
-    int8_t cleared = 0;
 
-    if (parse_cmdline(cmdline, &argc, &argv)) {
-        goto cleanup;
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
     }
 
-    while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) {
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) {
         switch (opt) {
         case 'c':
-            ly_ctx_unset_searchdir(*ctx, NULL);
-            cleared = 1;
+            yo->searchdir_unset = 1;
             break;
         case 'h':
             cmd_searchpath_help();
-            goto cleanup;
+            return 1;
         default:
             YLMSG_E("Unknown option.\n");
-            goto cleanup;
+            return 1;
         }
     }
 
-    if (!cleared && (argc == optind)) {
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    int rc = 0;
+
+    if (yo->searchdir_unset) {
+        ly_ctx_unset_searchdir(*ctx, NULL);
+    } else if (!yo->searchdir_unset && !posv) {
         /* no argument - print the paths */
         const char * const *dirs = ly_ctx_get_searchdirs(*ctx);
 
@@ -76,15 +88,9 @@
         for (uint32_t i = 0; dirs[i]; ++i) {
             printf("    %s\n", dirs[i]);
         }
-        goto cleanup;
+    } else {
+        rc = ly_ctx_set_searchdir(*ctx, posv);
     }
 
-    for (int i = 0; i < argc - optind; i++) {
-        if (ly_ctx_set_searchdir(*ctx, argv[optind + i])) {
-            goto cleanup;
-        }
-    }
-
-cleanup:
-    free_cmdline(argv);
+    return rc;
 }
diff --git a/tools/lint/cmd_verb.c b/tools/lint/cmd_verb.c
new file mode 100644
index 0000000..6d31c33
--- /dev/null
+++ b/tools/lint/cmd_verb.c
@@ -0,0 +1,114 @@
+/**
+ * @file cmd_verb.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief 'verb' command of the libyang's yanglint tool.
+ *
+ * Copyright (c) 2023-2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include "cmd.h"
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "yl_opt.h"
+
+void
+cmd_verb_help(void)
+{
+    printf("Usage: verb (error | warning | verbose | debug)\n");
+}
+
+int
+cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc)
+{
+    int rc = 0, argc = 0;
+    int opt, opt_index;
+    struct option options[] = {
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0}
+    };
+
+    if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) {
+        return rc;
+    }
+
+    while ((opt = getopt_long(argc, yo->argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) {
+        if (opt == 'h') {
+            cmd_verb_help();
+            return 1;
+        } else {
+            YLMSG_E("Unknown option.\n");
+            return 1;
+        }
+    }
+
+    *posv = &yo->argv[optind];
+    *posc = argc - optind;
+
+    return 0;
+}
+
+int
+cmd_verb_dep(struct yl_opt *yo, int posc)
+{
+    (void) yo;
+
+    if (posc > 1) {
+        YLMSG_E("Only a single verbosity level can be set.\n");
+        cmd_verb_help();
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv)
+{
+    (void) ctx, (void) yo;
+
+    if (!posv) {
+        /* no argument - print current value */
+        LY_LOG_LEVEL level = ly_log_level(LY_LLERR);
+
+        ly_log_level(level);
+        printf("Current verbosity level: ");
+        if (level == LY_LLERR) {
+            printf("error\n");
+        } else if (level == LY_LLWRN) {
+            printf("warning\n");
+        } else if (level == LY_LLVRB) {
+            printf("verbose\n");
+        } else if (level == LY_LLDBG) {
+            printf("debug\n");
+        }
+        return 0;
+    } else {
+        if (!strcasecmp("error", posv) || !strcmp("0", posv)) {
+            ly_log_level(LY_LLERR);
+        } else if (!strcasecmp("warning", posv) || !strcmp("1", posv)) {
+            ly_log_level(LY_LLWRN);
+        } else if (!strcasecmp("verbose", posv) || !strcmp("2", posv)) {
+            ly_log_level(LY_LLVRB);
+        } else if (!strcasecmp("debug", posv) || !strcmp("3", posv)) {
+            ly_log_level(LY_LLDBG);
+        } else {
+            YLMSG_E("Unknown verbosity \"%s\"\n", posv);
+            return 1;
+        }
+    }
+
+    return 0;
+}
diff --git a/tools/lint/common.c b/tools/lint/common.c
index fc9b1cd..bdf96e6 100644
--- a/tools/lint/common.c
+++ b/tools/lint/common.c
@@ -1,9 +1,10 @@
 /**
  * @file common.c
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode.
  *
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -19,7 +20,6 @@
 
 #include <assert.h>
 #include <errno.h>
-#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -27,6 +27,8 @@
 
 #include "compat.h"
 #include "libyang.h"
+#include "plugins_exts.h"
+#include "yl_opt.h"
 
 int
 parse_schema_path(const char *path, char **dir, char **module)
@@ -73,14 +75,11 @@
         return -1;
     }
 
-    if ((format_schema && !*format_schema) || (format_data && !*format_data)) {
-        /* get the file format */
-        if (get_format(filepath, format_schema, format_data)) {
-            return -1;
-        }
+    if (get_format(filepath, format_schema, format_data)) {
+        return -1;
     }
 
-    if (ly_in_new_filepath(filepath, 0, in)) {
+    if (in && ly_in_new_filepath(filepath, 0, in)) {
         YLMSG_E("Unable to process input file.\n");
         return -1;
     }
@@ -88,701 +87,81 @@
     return 0;
 }
 
-void
-free_features(void *flist)
-{
-    struct schema_features *rec = (struct schema_features *)flist;
-
-    if (rec) {
-        free(rec->mod_name);
-        if (rec->features) {
-            for (uint32_t u = 0; rec->features[u]; ++u) {
-                free(rec->features[u]);
-            }
-            free(rec->features);
-        }
-        free(rec);
-    }
-}
-
-void
-get_features(struct ly_set *fset, const char *module, const char ***features)
-{
-    /* get features list for this module */
-    for (uint32_t u = 0; u < fset->count; ++u) {
-        struct schema_features *sf = (struct schema_features *)fset->objs[u];
-
-        if (!strcmp(module, sf->mod_name)) {
-            /* matched module - explicitly set features */
-            *features = (const char **)sf->features;
-            sf->applied = 1;
-            return;
-        }
-    }
-
-    /* features not set so disable all */
-    *features = NULL;
-}
-
-int
-parse_features(const char *fstring, struct ly_set *fset)
-{
-    struct schema_features *rec;
-    uint32_t count;
-    char *p, **fp;
-
-    rec = calloc(1, sizeof *rec);
-    if (!rec) {
-        YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
-        return -1;
-    }
-    if (ly_set_add(fset, rec, 1, NULL)) {
-        YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
-        free(rec);
-        return -1;
-    }
-
-    /* fill the record */
-    p = strchr(fstring, ':');
-    if (!p) {
-        YLMSG_E("Invalid format of the features specification (%s).\n", fstring);
-        return -1;
-    }
-    rec->mod_name = strndup(fstring, p - fstring);
-
-    count = 0;
-    while (p) {
-        size_t len = 0;
-        char *token = p + 1;
-
-        p = strchr(token, ',');
-        if (!p) {
-            /* the last item, if any */
-            len = strlen(token);
-        } else {
-            len = p - token;
-        }
-
-        if (len) {
-            fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
-            if (!fp) {
-                YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
-                return -1;
-            }
-            rec->features = fp;
-            fp = &rec->features[count++]; /* array item to set */
-            (*fp) = strndup(token, len);
-        }
-    }
-
-    /* terminating NULL */
-    fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
-    if (!fp) {
-        YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
-        return -1;
-    }
-    rec->features = fp;
-    rec->features[count++] = NULL;
-
-    return 0;
-}
-
-struct cmdline_file *
-fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
-{
-    struct cmdline_file *rec;
-
-    rec = malloc(sizeof *rec);
-    if (!rec) {
-        YLMSG_E("Allocating memory for data file information failed.\n");
-        return NULL;
-    }
-    rec->in = in;
-    rec->path = path;
-    rec->format = format;
-
-    if (set && ly_set_add(set, rec, 1, NULL)) {
-        free(rec);
-        YLMSG_E("Storing data file information failed.\n");
-        return NULL;
-    }
-
-    return rec;
-}
-
-int
-collect_features(const struct lys_module *mod, struct ly_set *set)
-{
-    struct lysp_feature *f = NULL;
-    uint32_t idx = 0;
-
-    while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
-        if (ly_set_add(set, (void *)f->name, 1, NULL)) {
-            YLMSG_E("Memory allocation failed.\n");
-            ly_set_erase(set, NULL);
-            return 1;
-        }
-    }
-
-    return 0;
-}
-
-void
-print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set)
-{
-    size_t max_len;
-    uint32_t j;
-    const char *name;
-
-    /* header */
-    ly_print(out, "%s:\n", mod->name);
-
-    /* no features */
-    if (!set->count) {
-        ly_print(out, "\t(none)\n\n");
-        return;
-    }
-
-    /* get max len, so the statuses of all the features will be aligned */
-    max_len = 0;
-    for (j = 0; j < set->count; ++j) {
-        name = set->objs[j];
-        if (strlen(name) > max_len) {
-            max_len = strlen(name);
-        }
-    }
-
-    /* print features */
-    for (j = 0; j < set->count; ++j) {
-        name = set->objs[j];
-        ly_print(out, "\t%-*s (%s)\n", (int)max_len, name, lys_feature_value(mod, name) ? "off" : "on");
-    }
-
-    ly_print(out, "\n");
-}
-
-int
-generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param)
-{
-    uint32_t j;
-    /*
-     * features_len - length of all the features in the current module
-     * added_len - length of a string to be added, = features_len + extra necessary length
-     * param_len - length of the parameter before appending new string
-    */
-    size_t features_len, added_len, param_len;
-    char *tmp;
-
-    features_len = 0;
-    for (j = 0; j < set->count; j++) {
-        features_len += strlen(set->objs[j]);
-    }
-
-    if (j == 0) {
-        /* no features */
-        added_len = strlen("-F ") + strlen(mod->name) + strlen(":");
-    } else {
-        /* j = comma count, -1 because of trailing comma */
-        added_len = strlen("-F ") + strlen(mod->name) + strlen(":") + features_len + j - 1;
-    }
-
-    /* to avoid strlen(NULL) if this is the first call */
-    param_len = 0;
-    if (*features_param) {
-        param_len = strlen(*features_param);
-    }
-
-    /* +1 because of white space at the beginning */
-    tmp = realloc(*features_param, param_len + added_len + 1 + 1);
-    if (!tmp) {
-        goto error;
-    } else {
-        *features_param = tmp;
-    }
-    sprintf(*features_param + param_len, " -F %s:", mod->name);
-
-    for (j = 0; j < set->count; j++) {
-        strcat(*features_param, set->objs[j]);
-        /* no trailing comma */
-        if (j != (set->count - 1)) {
-            strcat(*features_param, ",");
-        }
-    }
-
-    return 0;
-
-error:
-    YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
-    return 1;
-}
-
-int
-print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param)
-{
-    int ret = 0;
-    uint32_t i = 0;
-    struct lys_module *mod;
-    struct ly_set set = {0};
-
-    while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
-        /* only care about implemented modules */
-        if (!mod->implemented) {
-            continue;
-        }
-
-        /* always erase the set, so the previous module's features don't carry over to the next module's features */
-        ly_set_erase(&set, NULL);
-
-        if (collect_features(mod, &set)) {
-            ret = 1;
-            goto cleanup;
-        }
-
-        if (generate_features && generate_features_output(mod, &set, features_param)) {
-            ret = 1;
-            goto cleanup;
-        }
-        print_features(out, mod, &set);
-    }
-
-cleanup:
-    ly_set_erase(&set, NULL);
-    return ret;
-}
-
-void
-free_cmdline_file(void *cmdline_file)
-{
-    struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
-
-    if (rec) {
-        ly_in_free(rec->in, 1);
-        free(rec);
-    }
-}
-
-void
-free_cmdline(char *argv[])
-{
-    if (argv) {
-        free(argv[0]);
-        free(argv);
-    }
-}
-
-int
-parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
-{
-    int count;
-    char **vector;
-    char *ptr;
-    char qmark = 0;
-
-    assert(cmdline);
-    assert(argc_p);
-    assert(argv_p);
-
-    /* init */
-    optind = 0; /* reinitialize getopt() */
-    count = 1;
-    vector = malloc((count + 1) * sizeof *vector);
-    vector[0] = strdup(cmdline);
-
-    /* command name */
-    strtok(vector[0], " ");
-
-    /* arguments */
-    while ((ptr = strtok(NULL, " "))) {
-        size_t len;
-        void *r;
-
-        len = strlen(ptr);
-
-        if (qmark) {
-            /* still in quotated text */
-            /* remove NULL termination of the previous token since it is not a token,
-             * but a part of the quotation string */
-            ptr[-1] = ' ';
-
-            if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
-                /* end of quotation */
-                qmark = 0;
-                /* shorten the argument by the terminating quotation mark */
-                ptr[len - 1] = '\0';
-            }
-            continue;
-        }
-
-        /* another token in cmdline */
-        ++count;
-        r = realloc(vector, (count + 1) * sizeof *vector);
-        if (!r) {
-            YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
-            free(vector);
-            return -1;
-        }
-        vector = r;
-        vector[count - 1] = ptr;
-
-        if ((ptr[0] == '"') || (ptr[0] == '\'')) {
-            /* remember the quotation mark to identify end of quotation */
-            qmark = ptr[0];
-
-            /* move the remembered argument after the quotation mark */
-            ++vector[count - 1];
-
-            /* check if the quotation is terminated within this token */
-            if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
-                /* end of quotation */
-                qmark = 0;
-                /* shorten the argument by the terminating quotation mark */
-                ptr[len - 1] = '\0';
-            }
-        }
-    }
-    vector[count] = NULL;
-
-    *argc_p = count;
-    *argv_p = vector;
-
-    return 0;
-}
-
-int
-get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
+LYS_INFORMAT
+get_schema_format(const char *filename)
 {
     char *ptr;
-    LYS_INFORMAT informat_s;
-    LYD_FORMAT informat_d;
 
-    /* get the file format */
     if ((ptr = strrchr(filename, '.')) != NULL) {
         ++ptr;
         if (!strcmp(ptr, "yang")) {
-            informat_s = LYS_IN_YANG;
-            informat_d = 0;
+            return LYS_IN_YANG;
         } else if (!strcmp(ptr, "yin")) {
-            informat_s = LYS_IN_YIN;
-            informat_d = 0;
-        } else if (!strcmp(ptr, "xml")) {
-            informat_s = 0;
-            informat_d = LYD_XML;
-        } else if (!strcmp(ptr, "json")) {
-            informat_s = 0;
-            informat_d = LYD_JSON;
-        } else if (!strcmp(ptr, "lyb")) {
-            informat_s = 0;
-            informat_d = LYD_LYB;
+            return LYS_IN_YIN;
         } else {
-            YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
-            return 0;
+            return LYS_IN_UNKNOWN;
         }
     } else {
-        YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
-        return 1;
+        return LYS_IN_UNKNOWN;
     }
-
-    if (informat_d) {
-        if (!data) {
-            YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
-            return 2;
-        }
-        (*data) = informat_d;
-    } else if (informat_s) {
-        if (!schema) {
-            YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
-            return 3;
-        }
-        (*schema) = informat_s;
-    }
-
-    return 0;
 }
 
-int
-print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
+LYD_FORMAT
+get_data_format(const char *filename)
 {
-    struct lyd_node *ylib;
-    uint32_t idx = 0, has_modules = 0;
-    const struct lys_module *mod;
+    char *ptr;
 
-    if (outformat != LYD_UNKNOWN) {
-        if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
-            YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n");
-            return 1;
-        }
-
-        lyd_print_all(out, ylib, outformat, 0);
-        lyd_free_all(ylib);
-        return 0;
-    }
-
-    /* iterate schemas in context and provide just the basic info */
-    ly_print(out, "List of the loaded models:\n");
-    while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
-        has_modules++;
-
-        /* conformance print */
-        if (mod->implemented) {
-            ly_print(out, "    I");
+    if ((ptr = strrchr(filename, '.')) != NULL) {
+        ++ptr;
+        if (!strcmp(ptr, "xml")) {
+            return LYD_XML;
+        } else if (!strcmp(ptr, "json")) {
+            return LYD_JSON;
+        } else if (!strcmp(ptr, "lyb")) {
+            return LYD_LYB;
         } else {
-            ly_print(out, "    i");
+            return LYD_UNKNOWN;
         }
-
-        /* module print */
-        ly_print(out, " %s", mod->name);
-        if (mod->revision) {
-            ly_print(out, "@%s", mod->revision);
-        }
-
-        /* submodules print */
-        if (mod->parsed && mod->parsed->includes) {
-            uint64_t u = 0;
-
-            ly_print(out, " (");
-            LY_ARRAY_FOR(mod->parsed->includes, u) {
-                ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
-                if (mod->parsed->includes[u].rev[0]) {
-                    ly_print(out, "@%s", mod->parsed->includes[u].rev);
-                }
-            }
-            ly_print(out, ")");
-        }
-
-        /* finish the line */
-        ly_print(out, "\n");
+    } else {
+        return LYD_UNKNOWN;
     }
-
-    if (!has_modules) {
-        ly_print(out, "\t(none)\n");
-    }
-
-    ly_print_flush(out);
-    return 0;
 }
 
 int
-evaluate_xpath(const struct lyd_node *tree, const char *xpath)
+get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form)
 {
-    struct ly_set *set = NULL;
+    LYS_INFORMAT schema;
+    LYD_FORMAT data;
 
-    if (lyd_find_xpath(tree, xpath, &set)) {
+    schema = !schema_form || !*schema_form ? LYS_IN_UNKNOWN : *schema_form;
+    data = !data_form || !*data_form ? LYD_UNKNOWN : *data_form;
+
+    if (!schema) {
+        schema = get_schema_format(filepath);
+    }
+    if (!data) {
+        data = get_data_format(filepath);
+    }
+
+    if (!schema && !data) {
+        YLMSG_E("Input schema format for %s file not recognized.", filepath);
+        return -1;
+    } else if (!data && !schema) {
+        YLMSG_E("Input data format for %s file not recognized.", filepath);
         return -1;
     }
+    assert(schema || data);
 
-    /* print result */
-    printf("XPath \"%s\" evaluation result:\n", xpath);
-    if (!set->count) {
-        printf("\tEmpty\n");
-    } else {
-        for (uint32_t u = 0; u < set->count; ++u) {
-            struct lyd_node *node = (struct lyd_node *)set->objs[u];
-
-            printf("  %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
-            if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
-                printf(" (value: \"%s\")\n", lyd_get_value(node));
-            } else if (node->schema->nodetype == LYS_LIST) {
-                printf(" (");
-                for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
-                    printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
-                            key->schema->name, lyd_get_value(key));
-                }
-                printf(")\n");
-            }
-        }
+    if (schema_form) {
+        *schema_form = schema;
+    }
+    if (data_form) {
+        *data_form = data;
     }
 
-    ly_set_free(set, NULL);
     return 0;
 }
 
-LY_ERR
-process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
-        uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
-        struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths)
-{
-    LY_ERR ret = LY_SUCCESS;
-    struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL;
-    char *path = NULL;
-    struct ly_set *set = NULL;
-
-    /* additional operational datastore */
-    if (operational_f && operational_f->in) {
-        ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &oper_tree);
-        if (ret) {
-            YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
-            goto cleanup;
-        }
-    }
-
-    for (uint32_t u = 0; u < inputs->count; ++u) {
-        struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
-
-        switch (data_type) {
-        case LYD_TYPE_DATA_YANG:
-            ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree);
-            break;
-        case LYD_TYPE_RPC_YANG:
-        case LYD_TYPE_REPLY_YANG:
-        case LYD_TYPE_NOTIF_YANG:
-            ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, &op);
-            break;
-        case LYD_TYPE_RPC_NETCONF:
-        case LYD_TYPE_NOTIF_NETCONF:
-            ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &envp, &op);
-
-            /* adjust pointers */
-            for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
-            break;
-        case LYD_TYPE_REPLY_NETCONF:
-            /* parse source RPC operation */
-            assert(rpc_f && rpc_f->in);
-            ret = lyd_parse_op(ctx, NULL, rpc_f->in, rpc_f->format, LYD_TYPE_RPC_NETCONF, &envp, &op);
-            if (ret) {
-                YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", rpc_f->path);
-                goto cleanup;
-            }
-
-            /* adjust pointers */
-            for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {}
-
-            /* free input */
-            lyd_free_siblings(lyd_child(op));
-
-            /* we do not care */
-            lyd_free_all(envp);
-            envp = NULL;
-
-            ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, data_type, &envp, NULL);
-            break;
-        default:
-            YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
-            goto cleanup;
-        }
-
-        if (ret) {
-            YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
-            goto cleanup;
-        }
-
-        if (merge) {
-            /* merge the data so far parsed for later validation and print */
-            if (!merged_tree) {
-                merged_tree = tree;
-            } else {
-                ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
-                if (ret) {
-                    YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
-                    goto cleanup;
-                }
-            }
-            tree = NULL;
-        } else if (format) {
-            /* print */
-            switch (data_type) {
-            case LYD_TYPE_DATA_YANG:
-                lyd_print_all(out, tree, format, options_print);
-                break;
-            case LYD_TYPE_RPC_YANG:
-            case LYD_TYPE_REPLY_YANG:
-            case LYD_TYPE_NOTIF_YANG:
-            case LYD_TYPE_RPC_NETCONF:
-            case LYD_TYPE_NOTIF_NETCONF:
-                lyd_print_tree(out, tree, format, options_print);
-                break;
-            case LYD_TYPE_REPLY_NETCONF:
-                /* just the output */
-                lyd_print_tree(out, lyd_child(tree), format, options_print);
-                break;
-            default:
-                assert(0);
-            }
-        } else {
-            /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */
-            switch (data_type) {
-            case LYD_TYPE_DATA_YANG:
-                /* already validated */
-                break;
-            case LYD_TYPE_RPC_YANG:
-            case LYD_TYPE_RPC_NETCONF:
-                ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL);
-                break;
-            case LYD_TYPE_REPLY_YANG:
-            case LYD_TYPE_REPLY_NETCONF:
-                ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL);
-                break;
-            case LYD_TYPE_NOTIF_YANG:
-            case LYD_TYPE_NOTIF_NETCONF:
-                ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL);
-                break;
-            default:
-                assert(0);
-            }
-            if (ret) {
-                if (operational_f->path) {
-                    YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
-                            input_f->path, operational_f->path);
-                } else {
-                    YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path);
-                }
-                goto cleanup;
-            }
-
-            if (op && oper_tree && lyd_parent(op)) {
-                /* check operation parent existence */
-                path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0);
-                if (!path) {
-                    ret = LY_EMEM;
-                    goto cleanup;
-                }
-                if ((ret = lyd_find_xpath(oper_tree, path, &set))) {
-                    goto cleanup;
-                }
-                if (!set->count) {
-                    YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path);
-                    ret = LY_EVALID;
-                    goto cleanup;
-                }
-            }
-        }
-
-        /* next iter */
-        lyd_free_all(tree);
-        tree = NULL;
-        lyd_free_all(envp);
-        envp = NULL;
-    }
-
-    if (merge) {
-        /* validate the merged result */
-        ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
-        if (ret) {
-            YLMSG_E("Merged data are not valid.\n");
-            goto cleanup;
-        }
-
-        if (format) {
-            /* and print it */
-            lyd_print_all(out, merged_tree, format, options_print);
-        }
-
-        for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) {
-            if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
-                goto cleanup;
-            }
-        }
-    }
-
-cleanup:
-    lyd_free_all(tree);
-    lyd_free_all(envp);
-    lyd_free_all(merged_tree);
-    lyd_free_all(oper_tree);
-    free(path);
-    ly_set_free(set, NULL);
-    return ret;
-}
-
 const struct lysc_node *
 find_schema_path(const struct ly_ctx *ctx, const char *schema_path)
 {
@@ -866,3 +245,42 @@
     free(module_name);
     return parent_node;
 }
+
+LY_ERR
+ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free)
+{
+    struct ly_ctx *ctx;
+    struct lyd_node *data = NULL;
+
+    ctx = ext->module->ctx;
+    if (user_data) {
+        lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data);
+    }
+
+    *ext_data = data;
+    *ext_data_free = 1;
+    return LY_SUCCESS;
+}
+
+LY_ERR
+searchpath_strcat(char **searchpaths, const char *path)
+{
+    uint64_t len;
+    char *new;
+
+    if (!(*searchpaths)) {
+        *searchpaths = strdup(path);
+        return LY_SUCCESS;
+    }
+
+    len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR);
+    new = realloc(*searchpaths, sizeof(char) * len + 1);
+    if (!new) {
+        return LY_EMEM;
+    }
+    strcat(new, PATH_SEPARATOR);
+    strcat(new, path);
+    *searchpaths = new;
+
+    return LY_SUCCESS;
+}
diff --git a/tools/lint/common.h b/tools/lint/common.h
index c3b8f95..340cecd 100644
--- a/tools/lint/common.h
+++ b/tools/lint/common.h
@@ -1,9 +1,10 @@
 /**
  * @file common.h
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode.
  *
- * Copyright (c) 2020 CESNET, z.s.p.o.
+ * Copyright (c) 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -55,90 +56,7 @@
 # define PATH_SEPARATOR ";"
 #endif
 
-/**
- * @brief Storage for the list of the features (their names) in a specific YANG module.
- */
-struct schema_features {
-    char *mod_name;
-    char **features;
-    ly_bool applied;
-};
-
-/**
- * @brief Data connected with a file provided on a command line as a file path.
- */
-struct cmdline_file {
-    struct ly_in *in;
-    const char *path;
-    LYD_FORMAT format;
-};
-
-/**
- * @brief Free the schema features list (struct schema_features *)
- * @param[in,out] flist The (struct schema_features *) to free.
- */
-void free_features(void *flist);
-
-/**
- * @brief Get the list of features connected with the specific YANG module.
- *
- * @param[in] fset The set of features information (struct schema_features *).
- * @param[in] module Name of the YANG module which features should be found.
- * @param[out] features Pointer to the list of features being returned.
- */
-void get_features(struct ly_set *fset, const char *module, const char ***features);
-
-/**
- * @brief Parse features being specified for the specific YANG module.
- *
- * Format of the input @p fstring is as follows: <module_name>:[<feature>,]*
- *
- * @param[in] fstring Input string to be parsed.
- * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled.
- */
-int parse_features(const char *fstring, struct ly_set *fset);
-
-/**
- * @brief Collect all features of a module.
- *
- * @param[in] mod Module to be searched for features.
- * @param[out] set Set in which the features will be stored.
- * @return 0 on success.
- * @return 1 on error.
- */
-int collect_features(const struct lys_module *mod, struct ly_set *set);
-
-/**
- * @brief Print all features of a single module.
- *
- * @param[in] out The output handler for printing.
- * @param[in] mod Module which contains the features.
- * @param[in] set Set which holds the features.
- */
-void print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set);
-
-/**
- * @brief Generate a string, which will contain features paramater.
- *
- * @param[in] mod Module, for which the string will be generated.
- * @param[in] set Set containing the features.
- * @param[out] features_param String which will contain the output.
- * @return 0 on success.
- * @return 1 on error.
- */
-int generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param);
-
-/**
- * @brief Print all features of all implemented modules.
- *
- * @param[in] out The output handler for printing.
- * @param[in] ctx Libyang context.
- * @param[in] generate_features Flag expressing whether to generate features parameter.
- * @param[out] features_param String, which will contain the output if the above flag is set.
- * @return 0 on success.
- * @return 1 on error.
- */
-int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param);
+struct cmdline_file;
 
 /**
  * @brief Parse path of a schema module file into the directory and module name.
@@ -146,7 +64,7 @@
  * @param[in] path Schema module file path to be parsed.
  * @param[out] dir Pointer to the directory path where the file resides. Caller is expected to free the returned string.
  * @param[out] module Pointer to the name of the module (without file suffixes or revision information) specified by the
- * @path. Caller is expected to free the returned string.
+ * @p path. Caller is expected to free the returned string.
  * @return 0 on success
  * @return -1 on error
  */
@@ -163,92 +81,40 @@
  * prohibited and such files are refused.
  * @param[out] format_data Format of the data detected from the file name. If NULL specified, the data formats are
  * prohibited and such files are refused.
- * @param[out] in Created input handler referring the file behind the @p filepath.
+ * @param[out] in Created input handler referring the file behind the @p filepath. Can be NULL.
  * @return 0 on success.
  * @return -1 on failure.
  */
 int get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in);
 
 /**
- * @brief Free the command line file data (struct cmdline_file *)
- * @param[in,out] cmdline_file The (struct cmdline_file *) to free.
- */
-void free_cmdline_file(void *cmdline_file);
-
-/**
- * @brief Create and fill the command line file data (struct cmdline_file *).
- * @param[in] set Optional parameter in case the record is supposed to be added into a set.
- * @param[in] in Input file handler.
- * @param[in] path Filepath of the file.
- * @param[in] format Format of the data file.
- * @return The created command line file structure.
- * @return NULL on failure
- */
-struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format);
-
-/**
- * @brief Helper function to prepare argc, argv pair from a command line string.
+ * @brief Get schema format of the @p filename's content according to the @p filename's suffix.
  *
- * @param[in] cmdline Complete command line string.
- * @param[out] argc_p Pointer to store argc value.
- * @param[out] argv_p Pointer to store argv vector.
- * @return 0 on success, non-zero on failure.
- */
-int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]);
-
-/**
- * @brief Destructor for the argument vector prepared by ::parse_cmdline().
- *
- * @param[in,out] argv Argument vector to destroy.
- */
-void free_cmdline(char *argv[]);
-
-/**
- * @brief Get expected format of the @p filename's content according to the @p filename's suffix.
  * @param[in] filename Name of the file to examine.
- * @param[out] schema Pointer to a variable to store the expected input schema format. Do not provide the pointer in case a
- * schema format is not expected.
- * @param[out] data Pointer to a variable to store the expected input data format. Do not provide the pointer in case a data
- * format is not expected.
+ * @return Detected schema input format.
+ */
+LYS_INFORMAT get_schema_format(const char *filename);
+
+/**
+ * @brief Get data format of the @p filename's content according to the @p filename's suffix.
+ *
+ * @param[in] filename Name of the file to examine.
+ * @return Detected data input format.
+ */
+LYD_FORMAT get_data_format(const char *filename);
+
+/**
+ * @brief Get format of the @p filename's content according to the @p filename's suffix.
+ *
+ * Either the @p schema or @p data parameter is set.
+ *
+ * @param[in] filepath Name of the file to examine.
+ * @param[out] schema_form Pointer to a variable to store the input schema format.
+ * @param[out] data_form Pointer to a variable to store the expected input data format.
  * @return zero in case a format was successfully detected.
  * @return nonzero in case it is not possible to get valid format from the @p filename.
  */
-int get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data);
-
-/**
- * @brief Print list of schemas in the context.
- *
- * @param[in] out Output handler where to print.
- * @param[in] ctx Context to print.
- * @param[in] outformat Optional output format. If not specified (:LYD_UNKNOWN), a simple list with single module per line
- * is printed. Otherwise, the ietf-yang-library data are printed in the specified format.
- * @return zero in case the data successfully printed.
- * @return nonzero in case of error.
- */
-int print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat);
-
-/**
- * @brief Process the input data files - parse, validate and print according to provided options.
- *
- * @param[in] ctx libyang context with schema.
- * @param[in] data_type The type of data in the input files.
- * @param[in] merge Flag if the data should be merged before validation.
- * @param[in] format Data format for printing.
- * @param[in] out The output handler for printing.
- * @param[in] options_parse Parser options.
- * @param[in] options_validate Validation options.
- * @param[in] options_print Printer options.
- * @param[in] operational_f Optional operational datastore file information for the case of an extended validation of
- * operation(s).
- * @param[in] rpc_f Source RPC operation file information for parsing NETCONF rpc-reply.
- * @param[in] inputs Set of file informations of input data files.
- * @param[in] xpath The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set
- * is printed. Alternative to data printing.
- * @return LY_ERR value.
- */
-LY_ERR process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
-        uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
-        struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths);
+int get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form);
 
 /**
  * @brief Get the node specified by the path.
@@ -257,6 +123,27 @@
  * @param[in] schema_path Path to the wanted node.
  * @return Pointer to the schema node specified by the path on success, NULL otherwise.
  */
-const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
+const struct lysc_node *find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
+
+/**
+ * @brief General callback providing run-time extension instance data.
+ *
+ * @param[in] ext Compiled extension instance.
+ * @param[in] user_data User-supplied callback data.
+ * @param[out] ext_data Provided extension instance data.
+ * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not.
+ * @return LY_ERR value.
+ */
+LY_ERR ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free);
+
+/**
+ * @brief Concatenation of paths into one string.
+ *
+ * @param[in,out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":"
+ * (on Windows, used semicolon ";" instead).
+ * @param[in] path Path to add.
+ * @return LY_ERR value.
+ */
+LY_ERR searchpath_strcat(char **searchpaths, const char *path);
 
 #endif /* COMMON_H_ */
diff --git a/tools/lint/completion.c b/tools/lint/completion.c
index 9843816..7a1842a 100644
--- a/tools/lint/completion.c
+++ b/tools/lint/completion.c
@@ -43,14 +43,18 @@
 {
     void *p;
 
-    ++(*match_count);
-    p = realloc(*matches, *match_count * sizeof **matches);
+    p = realloc(*matches, (*match_count + 1) * sizeof **matches);
     if (!p) {
         YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
         return;
     }
     *matches = p;
-    (*matches)[*match_count - 1] = strdup(match);
+    (*matches)[*match_count] = strdup(match);
+    if (!((*matches)[*match_count])) {
+        YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
+        return;
+    }
+    ++(*match_count);
 }
 
 /**
@@ -76,6 +80,34 @@
 }
 
 /**
+ * @brief Provides completion for arguments.
+ *
+ * @param[in] hint User input.
+ * @param[in] args Array of all possible arguments. The last element must be NULL.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count)
+{
+    int i;
+
+    *match_count = 0;
+    *matches = NULL;
+
+    for (i = 0; args[i]; i++) {
+        if (!strncmp(hint, args[i], strlen(hint))) {
+            cmd_completion_add_match(args[i], matches, match_count);
+        }
+    }
+    if (*match_count == 0) {
+        for (i = 0; args[i]; i++) {
+            cmd_completion_add_match(args[i], matches, match_count);
+        }
+    }
+}
+
+/**
  * @brief Provides completion for module names.
  *
  * @param[in] hint User input.
@@ -108,21 +140,21 @@
 /**
  * @brief Add all child nodes of a single node to the completion hint.
  *
- * @param[in] last_node Node of which children will be added to the hint.
- * @param matches[out] Matches provided to the user as a completion hint.
- * @param match_count[out] Number of matches.
+ * @param[in] parent Node of which children will be added to the hint.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
  */
 static void
-single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count)
+single_hint_add_children(const struct lysc_node *parent, char ***matches, unsigned int *match_count)
 {
     const struct lysc_node *node = NULL;
     char *match;
 
-    if (!last_node) {
+    if (!parent) {
         return;
     }
 
-    while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
+    while ((node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) {
         match = lysc_path(node, LYSC_PATH_LOG, NULL, 0);
         cmd_completion_add_match(match, matches, match_count);
         free(match);
@@ -277,6 +309,90 @@
 }
 
 /**
+ * @brief Get all possible argument hints for option.
+ *
+ * @param[in] hint User input.
+ * @param[out] matches Matches provided to the user as a completion hint.
+ * @param[out] match_count Number of matches.
+ */
+static void
+get_print_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"yang", "yin", "tree", "info", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"xml", "json", "lyb", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"xml", "json", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_verb_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"error", "warning", "verbose", "debug", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+#ifndef NDEBUG
+/**
+ * @copydoc get_print_format_arg
+ */
+static void
+get_debug_arg(const char *hint, char ***matches, unsigned int *match_count)
+{
+    const char *args[] = {"dict", "xpath", "dep-sets", NULL};
+
+    get_arg_completion(hint, args, matches, match_count);
+}
+
+#endif
+
+/**
  * @brief Get the string before the hint, which autocompletion is for.
  *
  * @param[in] buf Complete user input.
@@ -315,22 +431,37 @@
 complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc)
 {
     struct autocomplete {
-        const char *cmd;    /**< command */
-        const char *opt;    /**< optional option */
-        int last_opt;       /**< whether to autocomplete even if an option is last in the hint */
-
+        enum COMMAND_INDEX ci;      /**< command index to global variable 'commands' */
+        const char *opt;            /**< optional option */
         void (*ln_cb)(const char *, const char *, linenoiseCompletions *);  /**< linenoise callback to call */
         void (*yl_cb)(const char *, char ***, unsigned int *);              /**< yanglint callback to call */
     } ac[] = {
-        {"add", NULL, 1, linenoisePathCompletion, NULL},
-        {"searchpath", NULL, 0, linenoisePathCompletion, NULL},
-        {"data", NULL, 0, linenoisePathCompletion, NULL},
-        {"print", NULL, 0, NULL, get_model_completion},
-        {"feature", NULL, 0, NULL, get_model_completion},
-        {"print", "-P", 1, NULL, get_schema_completion},
+        {CMD_ADD,         NULL,    linenoisePathCompletion, NULL},
+        {CMD_PRINT,       "-f",    NULL, get_print_format_arg},
+        {CMD_PRINT,       "-P",    NULL, get_schema_completion},
+        {CMD_PRINT,       "-o",    linenoisePathCompletion, NULL},
+        {CMD_PRINT,       NULL,    NULL, get_model_completion},
+        {CMD_SEARCHPATH,  NULL,    linenoisePathCompletion, NULL},
+        {CMD_EXTDATA,     NULL,    linenoisePathCompletion, NULL},
+        {CMD_CLEAR,       "-Y",    linenoisePathCompletion, NULL},
+        {CMD_DATA,        "-t",    NULL, get_data_type_arg},
+        {CMD_DATA,        "-O",    linenoisePathCompletion, NULL},
+        {CMD_DATA,        "-R",    linenoisePathCompletion, NULL},
+        {CMD_DATA,        "-f",    NULL, get_data_in_format_arg},
+        {CMD_DATA,        "-F",    NULL, get_data_in_format_arg},
+        {CMD_DATA,        "-d",    NULL, get_data_default_arg},
+        {CMD_DATA,        "-o",    linenoisePathCompletion, NULL},
+        {CMD_DATA,        NULL,    linenoisePathCompletion, NULL},
+        {CMD_LIST,        NULL,    NULL, get_list_format_arg},
+        {CMD_FEATURE,     NULL,    NULL, get_model_completion},
+        {CMD_VERB,        NULL,    NULL, get_verb_arg},
+#ifndef NDEBUG
+        {CMD_DEBUG,       NULL,    NULL, get_debug_arg},
+#endif
     };
-    size_t cmd_len;
-    const char *last;
+    size_t name_len;
+    const char *last, *name, *getoptstr;
+    char opt[3] = {'\0', ':', '\0'};
     char **matches = NULL;
     unsigned int match_count = 0, i;
 
@@ -340,24 +471,27 @@
 
     } else {
         for (i = 0; i < (sizeof ac / sizeof *ac); ++i) {
-            cmd_len = strlen(ac[i].cmd);
-            if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) {
+            /* Find the right command. */
+            name = commands[ac[i].ci].name;
+            name_len = strlen(name);
+            if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) {
                 /* not this command */
                 continue;
             }
 
+            /* Select based on the right option. */
             last = get_last_str(buf, hint);
-            if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
-                /* autocompletion for (another) option */
+            opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0';
+            getoptstr = commands[ac[i].ci].optstring;
+            if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) {
+                /* completion for the argument must be defined */
                 continue;
-            }
-            if (!ac[i].last_opt && (last[0] == '-')) {
-                /* autocompletion for the command, not an option */
+            } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) {
+                /* completion for (another) option */
                 continue;
-            }
-            if ((last != buf) && (last[0] != '-')) {
-                /* autocompleted */
-                return;
+            } else if (ac[i].opt && !opt[0]) {
+                /* completion is defined for option */
+                continue;
             }
 
             /* callback */
diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md
index 36cecd1..93d3c2a 100644
--- a/tools/lint/examples/README.md
+++ b/tools/lint/examples/README.md
@@ -33,11 +33,11 @@
 ```
 > help searchpath
 Usage: searchpath [--clear] [<modules-dir-path> ...]
-                  Set paths of directories where to search for imports and
-                  includes of the schema modules. The current working directory
-                  and the path of the module being added is used implicitly.
-                  The 'load' command uses these paths to search even for the
-                  schema modules to be loaded.
+                  Set paths of directories where to search for imports and includes
+                  of the schema modules. Subdirectories are also searched. The current
+                  working directory and the path of the module being added is used implicitly.
+                  The 'load' command uses these paths to search even for the schema modules
+                  to be loaded.
 ```
 
 The input files referred in this document are available together with this
diff --git a/tools/lint/main.c b/tools/lint/main.c
index 9f0d027..987ea10 100644
--- a/tools/lint/main.c
+++ b/tools/lint/main.c
@@ -1,9 +1,10 @@
 /**
  * @file main.c
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's yanglint tool
  *
- * Copyright (c) 2015-2020 CESNET, z.s.p.o.
+ * Copyright (c) 2015-2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -27,6 +28,7 @@
 #include "completion.h"
 #include "configuration.h"
 #include "linenoise/linenoise.h"
+#include "yl_opt.h"
 
 int done;
 struct ly_ctx *ctx = NULL;
@@ -37,13 +39,17 @@
 int
 main(int argc, char *argv[])
 {
-    char *cmdline;
-    int cmdlen;
+    int cmdlen, posc, i, j;
+    struct yl_opt yo = {0};
+    char *empty = NULL, *cmdline;
+    char **posv;
+    uint8_t cmd_found;
 
     if (argc > 1) {
         /* run in non-interactive mode */
         return main_ni(argc, argv);
     }
+    yo.interactive = 1;
 
     /* continue in interactive mode */
     linenoiseSetCompletionCallback(complete_cmd);
@@ -55,7 +61,10 @@
     }
 
     while (!done) {
-        uint8_t executed = 0;
+        cmd_found = 0;
+
+        posv = &empty;
+        posc = 0;
 
         /* get the command from user */
         cmdline = linenoise(PROMPT);
@@ -76,25 +85,48 @@
         for (cmdlen = 0; cmdline[cmdlen] && (cmdline[cmdlen] != ' '); cmdlen++) {}
 
         /* execute the command if any valid specified */
-        for (uint16_t i = 0; commands[i].name; i++) {
+        for (i = 0; commands[i].name; i++) {
             if (strncmp(cmdline, commands[i].name, (size_t)cmdlen) || (commands[i].name[cmdlen] != '\0')) {
                 continue;
             }
 
-            commands[i].func(&ctx, cmdline);
-            executed = 1;
+            cmd_found = 1;
+            if (commands[i].opt_func && commands[i].opt_func(&yo, cmdline, &posv, &posc)) {
+                break;
+            }
+            if (commands[i].dep_func && commands[i].dep_func(&yo, posc)) {
+                break;
+            }
+            if (posc) {
+                for (j = 0; j < posc; j++) {
+                    yo.last_one = (j + 1) == posc;
+                    if (commands[i].exec_func(&ctx, &yo, posv[j])) {
+                        break;
+                    }
+                }
+            } else {
+                commands[i].exec_func(&ctx, &yo, NULL);
+            }
+            if (commands[i].fin_func) {
+                commands[i].fin_func(ctx, &yo);
+            }
+
             break;
         }
 
-        if (!executed) {
+        if (!cmd_found) {
             /* if unknown command specified, tell it to user */
             YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.\n", cmdlen, cmdline);
         }
 
         linenoiseHistoryAdd(cmdline);
         free(cmdline);
+        yl_opt_erase(&yo);
     }
 
+    /* Global variables in commands are freed. */
+    cmd_free();
+
     store_config();
     ly_ctx_destroy(ctx);
 
diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c
index 6e00339..6f04623 100644
--- a/tools/lint/main_ni.c
+++ b/tools/lint/main_ni.c
@@ -2,9 +2,10 @@
  * @file main_ni.c
  * @author Radek Krejci <rkrejci@cesnet.cz>
  * @author Michal Vasko <mvasko@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's yanglint tool - non-interactive code
  *
- * Copyright (c) 2020 - 2022 CESNET, z.s.p.o.
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -25,107 +26,13 @@
 #include <sys/stat.h>
 
 #include "libyang.h"
-#include "plugins_exts.h"
 
+#include "cmd.h"
 #include "common.h"
 #include "out.h"
 #include "tools/config.h"
-
-/**
- * @brief Context structure to hold and pass variables in a structured form.
- */
-struct context {
-    /* libyang context for the run */
-    const char *yang_lib_file;
-    uint16_t ctx_options;
-    struct ly_ctx *ctx;
-
-    /* prepared output (--output option or stdout by default) */
-    struct ly_out *out;
-
-    char *searchpaths;
-
-    /* options flags */
-    uint8_t list;        /* -l option to print list of schemas */
-
-    /* line length for 'tree' format */
-    size_t line_length; /* --tree-line-length */
-
-    /*
-     * schema
-     */
-    /* set schema modules' features via --features option (struct schema_features *) */
-    struct ly_set schema_features;
-
-    /* set of loaded schema modules (struct lys_module *) */
-    struct ly_set schema_modules;
-
-    /* options to parse and print schema modules */
-    uint32_t schema_parse_options;
-    uint32_t schema_print_options;
-
-    /* specification of printing schema node subtree, option --schema-node */
-    const char *schema_node_path;
-    const struct lysc_node *schema_node;
-    const char *submodule;
-
-    /* name of file containing explicit context passed to callback
-     * for schema-mount extension.  This also causes a callback to
-     * be registered.
-     */
-    char *schema_context_filename;
-
-    /* value of --format in case of schema format */
-    LYS_OUTFORMAT schema_out_format;
-    ly_bool feature_param_format;
-
-    /*
-     * data
-     */
-    /* various options based on --type option */
-    enum lyd_type data_type;
-    uint32_t data_parse_options;
-    uint32_t data_validate_options;
-    uint32_t data_print_options;
-
-    /* flag for --merge option */
-    uint8_t data_merge;
-
-    /* value of --format in case of data format */
-    LYD_FORMAT data_out_format;
-
-    /* input data files (struct cmdline_file *) */
-    struct ly_set data_inputs;
-
-    /* storage for --operational */
-    struct cmdline_file data_operational;
-
-    /* storage for --reply-rpc */
-    struct cmdline_file reply_rpc;
-};
-
-static void
-erase_context(struct context *c)
-{
-    /* data */
-    ly_set_erase(&c->data_inputs, free_cmdline_file);
-    ly_in_free(c->data_operational.in, 1);
-
-    /* schema */
-    ly_set_erase(&c->schema_features, free_features);
-    ly_set_erase(&c->schema_modules, NULL);
-
-    /* context */
-    free(c->searchpaths);
-    c->searchpaths = NULL;
-
-    ly_out_free(c->out, NULL,  0);
-    ly_ctx_destroy(c->ctx);
-
-    if (c->schema_context_filename) {
-        free(c->schema_context_filename);
-    }
-}
+#include "yl_opt.h"
+#include "yl_schema_features.h"
 
 static void
 version(void)
@@ -146,7 +53,8 @@
             "        printing them in the specified format.\n\n"
             "    yanglint -t (nc-)rpc/notif [-O <operational-file>] <schema>... <file>\n"
             "        Validates the YANG/NETCONF RPC/notification <file> according to the <schema>(s) using\n"
-            "        <operational-file> with possible references to the operational datastore data.\n\n"
+            "        <operational-file> with possible references to the operational datastore data.\n"
+            "        To validate nested-notification or action, the <operational-file> is required.\n\n"
             "    yanglint -t nc-reply -R <rpc-file> [-O <operational-file>] <schema>... <file>\n"
             "        Validates the NETCONF rpc-reply <file> of RPC <rpc-file> according to the <schema>(s)\n"
             "        using <operational-file> with possible references to the operational datastore data.\n\n"
@@ -169,10 +77,16 @@
             "                yang, yin, tree, info and feature-param for schemas,\n"
             "                xml, json, and lyb for data.\n\n");
 
+    printf("  -I FORMAT, --in-format=FORMAT\n"
+            "                Load the data in one of the following formats:\n"
+            "                xml, json, lyb\n"
+            "                If input format not specified, it is detected from the file extension.\n\n");
+
     printf("  -p PATH, --path=PATH\n"
             "                Search path for schema (YANG/YIN) modules. The option can be\n"
             "                used multiple times. The current working directory and the\n"
-            "                path of the module being added is used implicitly.\n\n");
+            "                path of the module being added is used implicitly. Subdirectories\n"
+            "                are also searched\n\n");
 
     printf("  -D, --disable-searchdir\n"
             "                Do not implicitly search in current working directory for\n"
@@ -251,6 +165,13 @@
             "      trim            - Remove all nodes with a default value.\n"
             "      implicit-tagged - Add missing nodes and mark them with the attribute.\n\n");
 
+    printf("  -E XPATH, --data-xpath=XPATH\n"
+            "                Evaluate XPATH expression over the data and print the nodes satisfying\n"
+            "                the expression. The output format is specific and the option cannot\n"
+            "                be combined with the -f and -d options. Also all the data\n"
+            "                inputs are merged into a single data tree where the expression\n"
+            "                is evaluated, so the -m option is always set implicitly.\n\n");
+
     printf("  -l, --list    Print info about the loaded schemas.\n"
             "                (i - imported module, I - implemented module)\n"
             "                In case the '-f' option with data encoding is specified,\n"
@@ -267,8 +188,8 @@
             "                Provide optional data to extend validation of the '(nc-)rpc',\n"
             "                '(nc-)reply' or '(nc-)notif' TYPEs. The FILE is supposed to contain\n"
             "                the operational datastore referenced from the operation.\n"
-            "                In case of a nested operation, its parent existence is also\n"
-            "                checked in these operational data.\n\n");
+            "                In case of a nested notification or action, its parent existence\n"
+            "                is also checked in these operational data.\n\n");
 
     printf("  -R FILE, --reply-rpc=FILE\n"
             "                Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n"
@@ -287,6 +208,9 @@
             "                create an exact YANG schema context. If specified, the '-F'\n"
             "                parameter (enabled features) is ignored.\n\n");
 
+    printf("  -X, --extended-leafref\n"
+            "                Allow usage of deref() XPath function within leafref\n\n");
+
 #ifndef NDEBUG
     printf("  -G GROUPS, --debug=GROUPS\n"
             "                Enable printing of specific debugging message group\n"
@@ -321,11 +245,11 @@
     }
 }
 
-static struct schema_features *
+static struct yl_schema_features *
 get_features_not_applied(const struct ly_set *fset)
 {
     for (uint32_t u = 0; u < fset->count; ++u) {
-        struct schema_features *sf = fset->objs[u];
+        struct yl_schema_features *sf = fset->objs[u];
 
         if (!sf->applied) {
             return sf;
@@ -335,191 +259,167 @@
     return NULL;
 }
 
-static LY_ERR
-ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free)
-{
-    struct ly_ctx *ctx;
-    struct lyd_node *data = NULL;
-
-    ctx = ext->module->ctx;
-    if (user_data) {
-        lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data);
-    }
-
-    *ext_data = data;
-    *ext_data_free = 1;
-    return LY_SUCCESS;
-}
-
-static LY_ERR
-searchpath_strcat(char **searchpaths, const char *path)
-{
-    uint64_t len;
-    char *new;
-
-    if (!(*searchpaths)) {
-        *searchpaths = strdup(path);
-        return LY_SUCCESS;
-    }
-
-    len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR);
-    new = realloc(*searchpaths, sizeof(char) * len + 1);
-    if (!new) {
-        return LY_EMEM;
-    }
-    strcat(new, PATH_SEPARATOR);
-    strcat(new, path);
-    *searchpaths = new;
-
-    return LY_SUCCESS;
-}
-
+/**
+ * @brief Create the libyang context.
+ *
+ * @param[in] yang_lib_file Context can be defined in yang library file.
+ * @param[in] searchpaths Directories in which modules are searched.
+ * @param[in,out] schema_features Set of features.
+ * @param[in,out] ctx_options Options for libyang context.
+ * @param[out] ctx Context for libyang.
+ * @return 0 on success.
+ */
 static int
-fill_context_inputs(int argc, char *argv[], struct context *c)
+create_ly_context(const char *yang_lib_file, const char *searchpaths, struct ly_set *schema_features,
+        uint16_t *ctx_options, struct ly_ctx **ctx)
 {
-    struct ly_in *in = NULL;
-    struct schema_features *sf;
-    struct lys_module *mod;
-    const char *all_features[] = {"*", NULL};
-    char *dir = NULL, *module = NULL;
-
-    /* Create libyang context. */
-    if (c->yang_lib_file) {
+    if (yang_lib_file) {
         /* ignore features */
-        ly_set_erase(&c->schema_features, free_features);
+        ly_set_erase(schema_features, yl_schema_features_free);
 
-        if (ly_ctx_new_ylpath(c->searchpaths, c->yang_lib_file, LYD_UNKNOWN, c->ctx_options, &c->ctx)) {
+        if (ly_ctx_new_ylpath(searchpaths, yang_lib_file, LYD_UNKNOWN, *ctx_options, ctx)) {
             YLMSG_E("Unable to modify libyang context with yang-library data.\n");
             return -1;
         }
     } else {
         /* set imp feature flag if all should be enabled */
-        c->ctx_options |= !c->schema_features.count ? LY_CTX_ENABLE_IMP_FEATURES : 0;
+        (*ctx_options) |= !schema_features->count ? LY_CTX_ENABLE_IMP_FEATURES : 0;
 
-        if (ly_ctx_new(c->searchpaths, c->ctx_options, &c->ctx)) {
+        if (ly_ctx_new(searchpaths, *ctx_options, ctx)) {
             YLMSG_E("Unable to create libyang context\n");
             return -1;
         }
     }
 
-    /* set callback providing run-time extension instance data */
-    if (c->schema_context_filename) {
-        ly_ctx_set_ext_data_clb(c->ctx, ext_data_clb, c->schema_context_filename);
-    }
+    return 0;
+}
 
-    /* process the operational and/or reply RPC content if any */
-    if (c->data_operational.path) {
-        if (get_input(c->data_operational.path, NULL, &c->data_operational.format, &c->data_operational.in)) {
-            return -1;
-        }
-    }
-    if (c->reply_rpc.path) {
-        if (get_input(c->reply_rpc.path, NULL, &c->reply_rpc.format, &c->reply_rpc.in)) {
-            return -1;
-        }
-    }
-
-    for (int i = 0; i < argc - optind; i++) {
-        LYS_INFORMAT format_schema = LYS_IN_UNKNOWN;
-        LYD_FORMAT format_data = LYD_UNKNOWN;
-
-        if (get_input(argv[optind + i], &format_schema, &format_data, &in)) {
-            goto error;
-        }
-
-        if (format_schema) {
-            LY_ERR ret;
-            uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */
-            const char **features;
-
-            /* parse the input */
-            if (parse_schema_path(argv[optind + i], &dir, &module)) {
-                goto error;
-            }
-
-            if (c->yang_lib_file) {
-                /* just get the module, it should already be parsed */
-                mod = ly_ctx_get_module_implemented(c->ctx, module);
-                if (!mod) {
-                    YLMSG_E("Schema module \"%s\" not implemented in yang-library data.\n", module);
-                    goto error;
-                }
-            } else {
-                /* add temporarily also the path of the module itself */
-                if (ly_ctx_set_searchdir(c->ctx, dir) == LY_EEXIST) {
-                    path_unset = 0;
-                }
-
-                /* get features list for this module */
-                if (!c->schema_features.count) {
-                    features = all_features;
-                } else {
-                    get_features(&c->schema_features, module, &features);
-                }
-
-                /* parse module */
-                ret = lys_parse(c->ctx, in, format_schema, features, &mod);
-                ly_ctx_unset_searchdir_last(c->ctx, path_unset);
-                if (ret) {
-                    YLMSG_E("Parsing schema module \"%s\" failed.\n", argv[optind + i]);
-                    goto error;
-                }
-            }
-
-            /* temporary cleanup */
-            free(dir);
-            dir = NULL;
-            free(module);
-            module = NULL;
-
-            if (c->schema_out_format || c->feature_param_format) {
-                /* module will be printed */
-                if (ly_set_add(&c->schema_modules, (void *)mod, 1, NULL)) {
-                    YLMSG_E("Storing parsed schema module (%s) for print failed.\n", argv[optind + i]);
-                    goto error;
-                }
-            }
-        } else if (format_data) {
-            if (!fill_cmdline_file(&c->data_inputs, in, argv[optind + i], format_data)) {
-                goto error;
-            }
-            in = NULL;
-        }
-
-        ly_in_free(in, 1);
-        in = NULL;
-    }
+/**
+ * @brief Implement module if some feature has not been applied.
+ *
+ * @param[in] schema_features Set of features.
+ * @param[in,out] ctx Context for libyang.
+ * @return 0 on success.
+ */
+static int
+apply_features(struct ly_set *schema_features, struct ly_ctx *ctx)
+{
+    struct yl_schema_features *sf;
+    struct lys_module *mod;
 
     /* check that all specified features were applied, apply now if possible */
-    while ((sf = get_features_not_applied(&c->schema_features))) {
+    while ((sf = get_features_not_applied(schema_features))) {
         /* try to find implemented or the latest revision of this module */
-        mod = ly_ctx_get_module_implemented(c->ctx, sf->mod_name);
+        mod = ly_ctx_get_module_implemented(ctx, sf->mod_name);
         if (!mod) {
-            mod = ly_ctx_get_module_latest(c->ctx, sf->mod_name);
+            mod = ly_ctx_get_module_latest(ctx, sf->mod_name);
         }
         if (!mod) {
             YLMSG_E("Specified features not applied, module \"%s\" not loaded.\n", sf->mod_name);
-            goto error;
+            return 1;
         }
 
         /* we have the module, implement it if needed and enable the specific features */
         if (lys_set_implemented(mod, (const char **)sf->features)) {
             YLMSG_E("Implementing module \"%s\" failed.\n", mod->name);
-            goto error;
+            return 1;
         }
         sf->applied = 1;
     }
 
     return 0;
-
-error:
-    ly_in_free(in, 1);
-    free(dir);
-    free(module);
-    return -1;
 }
 
 /**
+ * @brief Parse and compile modules, data are only stored for later processing.
+ *
+ * @param[in] argc Number of strings in @p argv.
+ * @param[in] argv Strings from command line.
+ * @param[in] optind Index to the first input file in @p argv.
+ * @param[in] data_in_format Specified input data format.
+ * @param[in,out] ctx Context for libyang.
+ * @param[in,out] yo Options for yanglint.
+ * @return 0 on success.
+ */
+static int
+fill_context_inputs(int argc, char *argv[], int optind, LYD_FORMAT data_in_format, struct ly_ctx *ctx,
+        struct yl_opt *yo)
+{
+    char *filepath = NULL;
+    LYS_INFORMAT format_schema;
+    LYD_FORMAT format_data;
+
+    for (int i = 0; i < argc - optind; i++) {
+        format_schema = LYS_IN_UNKNOWN;
+        format_data = data_in_format;
+
+        filepath = argv[optind + i];
+
+        if (!filepath) {
+            return -1;
+        }
+        if (get_format(filepath, &format_schema, &format_data)) {
+            return -1;
+        }
+
+        if (format_schema) {
+            if (cmd_add_exec(&ctx, yo, filepath)) {
+                return -1;
+            }
+        } else {
+            if (cmd_data_store(&ctx, yo, filepath)) {
+                return -1;
+            }
+        }
+    }
+
+    /* Check that all specified features were applied, apply now if possible. */
+    if (apply_features(&yo->schema_features, ctx)) {
+        return -1;
+    }
+
+    return 0;
+}
+
+#ifndef NDEBUG
+/**
+ * @brief Enable specific debugging messages.
+ *
+ * @param[in] groups String in the form "<group>[,group>]*".
+ * @param[in,out] yo Options for yanglint.
+ * return 0 on success.
+ */
+static int
+set_debug_groups(char *groups, struct yl_opt *yo)
+{
+    int rc;
+    char *str, *end;
+
+    /* Process all debug arguments except the last one. */
+    for (str = groups; (end = strchr(str, ',')); str = end + 1) {
+        /* Temporary modify input string. */
+        *end = '\0';
+        rc = cmd_debug_store(NULL, yo, str);
+        *end = ',';
+        if (rc) {
+            return -1;
+        }
+    }
+    /* Process single/last debug argument. */
+    if (cmd_debug_store(NULL, yo, str)) {
+        return -1;
+    }
+    /* All debug arguments are valid, so they can apply. */
+    if (cmd_debug_setlog(NULL, yo)) {
+        return -1;
+    }
+
+    return 0;
+}
+
+#endif
+
+/**
  * @brief Process command line options and store the settings into the context.
  *
  * return -1 in case of error;
@@ -527,10 +427,8 @@
  * return 1 in case of success, but expect to exit.
  */
 static int
-fill_context(int argc, char *argv[], struct context *c)
+fill_context(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx)
 {
-    int ret;
-
     int opt, opt_index;
     struct option options[] = {
         {"help",              no_argument,       NULL, 'h'},
@@ -542,6 +440,7 @@
         {"disable-searchdir", no_argument,       NULL, 'D'},
         {"features",          required_argument, NULL, 'F'},
         {"make-implemented",  no_argument,       NULL, 'i'},
+        {"in-format",         required_argument, NULL, 'I'},
         {"schema-node",       required_argument, NULL, 'P'},
         {"single-node",       no_argument,       NULL, 'q'},
         {"submodule",         required_argument, NULL, 's'},
@@ -550,6 +449,7 @@
         {"present",           no_argument,       NULL, 'e'},
         {"type",              required_argument, NULL, 't'},
         {"default",           required_argument, NULL, 'd'},
+        {"data-xpath",        required_argument, NULL, 'E'},
         {"list",              no_argument,       NULL, 'l'},
         {"tree-line-length",  required_argument, NULL, 'L'},
         {"output",            required_argument, NULL, 'o'},
@@ -558,6 +458,7 @@
         {"merge",             no_argument,       NULL, 'm'},
         {"yang-library",      no_argument,       NULL, 'y'},
         {"yang-library-file", required_argument, NULL, 'Y'},
+        {"extended-leafref",  no_argument,       NULL, 'X'},
 #ifndef NDEBUG
         {"debug",            required_argument, NULL, 'G'},
 #endif
@@ -565,16 +466,16 @@
     };
     uint8_t data_type_set = 0;
 
-    c->ctx_options = YL_DEFAULT_CTX_OPTIONS;
-    c->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
-    c->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
-    c->line_length = 0;
+    yo->ctx_options = YL_DEFAULT_CTX_OPTIONS;
+    yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS;
+    yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS;
+    yo->line_length = 0;
 
     opterr = 0;
 #ifndef NDEBUG
-    while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:G:", options, &opt_index)) != -1)
+    while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:G:", options, &opt_index)) != -1)
 #else
-    while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:", options, &opt_index)) != -1)
+    while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:", options, &opt_index)) != -1)
 #endif
     {
         switch (opt) {
@@ -610,105 +511,67 @@
         } /* case 'Q' */
 
         case 'f': /* --format */
-            if (!strcasecmp(optarg, "yang")) {
-                c->schema_out_format = LYS_OUT_YANG;
-                c->data_out_format = 0;
-            } else if (!strcasecmp(optarg, "yin")) {
-                c->schema_out_format = LYS_OUT_YIN;
-                c->data_out_format = 0;
-            } else if (!strcasecmp(optarg, "info")) {
-                c->schema_out_format = LYS_OUT_YANG_COMPILED;
-                c->data_out_format = 0;
-            } else if (!strcasecmp(optarg, "tree")) {
-                c->schema_out_format = LYS_OUT_TREE;
-                c->data_out_format = 0;
-            } else if (!strcasecmp(optarg, "xml")) {
-                c->schema_out_format = 0;
-                c->data_out_format = LYD_XML;
-            } else if (!strcasecmp(optarg, "json")) {
-                c->schema_out_format = 0;
-                c->data_out_format = LYD_JSON;
-            } else if (!strcasecmp(optarg, "lyb")) {
-                c->schema_out_format = 0;
-                c->data_out_format = LYD_LYB;
-            } else if (!strcasecmp(optarg, "feature-param")) {
-                c->feature_param_format = 1;
-            } else {
-                YLMSG_E("Unknown output format %s\n", optarg);
+            if (yl_opt_update_out_format(optarg, yo)) {
                 help(1);
                 return -1;
             }
             break;
 
-        case 'p': { /* --path */
-            struct stat st;
-
-            if (stat(optarg, &st) == -1) {
-                YLMSG_E("Unable to use search path (%s) - %s.\n", optarg, strerror(errno));
+        case 'I': /* --in-format */
+            if (yo_opt_update_data_in_format(optarg, yo)) {
+                YLMSG_E("Unknown input format %s\n", optarg);
+                help(1);
                 return -1;
             }
-            if (!S_ISDIR(st.st_mode)) {
-                YLMSG_E("Provided search path is not a directory.\n");
-                return -1;
-            }
+            break;
 
-            if (searchpath_strcat(&c->searchpaths, optarg)) {
+        case 'p':   /* --path */
+            if (searchpath_strcat(&yo->searchpaths, optarg)) {
                 YLMSG_E("Storing searchpath failed.\n");
                 return -1;
             }
-
             break;
-        } /* case 'p' */
+        /* case 'p' */
 
-        case 'D': /* --disable-search */
-            if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
+        case 'D': /* --disable-searchdir */
+            if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) {
                 YLMSG_W("The -D option specified too many times.\n");
             }
-            if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) {
-                c->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
-                c->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS;
-            } else {
-                c->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD;
-            }
+            yo_opt_update_disable_searchdir(yo);
             break;
 
         case 'F': /* --features */
-            if (parse_features(optarg, &c->schema_features)) {
+            if (parse_features(optarg, &yo->schema_features)) {
                 return -1;
             }
             break;
 
         case 'i': /* --make-implemented */
-            if (c->ctx_options & LY_CTX_REF_IMPLEMENTED) {
-                c->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
-                c->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
-            } else {
-                c->ctx_options |= LY_CTX_REF_IMPLEMENTED;
-            }
+            yo_opt_update_make_implemented(yo);
             break;
 
         case 'P': /* --schema-node */
-            c->schema_node_path = optarg;
+            yo->schema_node_path = optarg;
             break;
 
         case 'q': /* --single-node */
-            c->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
+            yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT;
             break;
 
         case 's': /* --submodule */
-            c->submodule = optarg;
+            yo->submodule = optarg;
             break;
 
         case 'x': /* --ext-data */
-            c->schema_context_filename = strdup(optarg);
+            yo->schema_context_filename = optarg;
             break;
 
         case 'n': /* --not-strict */
-            c->data_parse_options &= ~LYD_PARSE_STRICT;
+            yo->data_parse_options &= ~LYD_PARSE_STRICT;
             break;
 
         case 'e': /* --present */
-            c->data_validate_options |= LYD_VALIDATE_PRESENT;
+            yo->data_validate_options |= LYD_VALIDATE_PRESENT;
             break;
 
         case 't': /* --type */
@@ -717,30 +580,7 @@
                 return -1;
             }
 
-            if (!strcasecmp(optarg, "config")) {
-                c->data_parse_options |= LYD_PARSE_NO_STATE;
-                c->data_validate_options |= LYD_VALIDATE_NO_STATE;
-            } else if (!strcasecmp(optarg, "get")) {
-                c->data_parse_options |= LYD_PARSE_ONLY;
-            } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config")) {
-                c->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
-            } else if (!strcasecmp(optarg, "edit")) {
-                c->data_parse_options |= LYD_PARSE_ONLY;
-            } else if (!strcasecmp(optarg, "rpc")) {
-                c->data_type = LYD_TYPE_RPC_YANG;
-            } else if (!strcasecmp(optarg, "nc-rpc")) {
-                c->data_type = LYD_TYPE_RPC_NETCONF;
-            } else if (!strcasecmp(optarg, "reply")) {
-                c->data_type = LYD_TYPE_REPLY_YANG;
-            } else if (!strcasecmp(optarg, "nc-reply")) {
-                c->data_type = LYD_TYPE_REPLY_NETCONF;
-            } else if (!strcasecmp(optarg, "notif")) {
-                c->data_type = LYD_TYPE_NOTIF_YANG;
-            } else if (!strcasecmp(optarg, "nc-notif")) {
-                c->data_type = LYD_TYPE_NOTIF_NETCONF;
-            } else if (!strcasecmp(optarg, "data")) {
-                /* default option */
-            } else {
+            if (yl_opt_update_data_type(optarg, yo)) {
                 YLMSG_E("Unknown data tree type %s\n", optarg);
                 help(1);
                 return -1;
@@ -750,35 +590,34 @@
             break;
 
         case 'd': /* --default */
-            if (!strcasecmp(optarg, "all")) {
-                c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
-            } else if (!strcasecmp(optarg, "all-tagged")) {
-                c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
-            } else if (!strcasecmp(optarg, "trim")) {
-                c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
-            } else if (!strcasecmp(optarg, "implicit-tagged")) {
-                c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
-            } else {
+            if (yo_opt_update_data_default(optarg, yo)) {
                 YLMSG_E("Unknown default mode %s\n", optarg);
                 help(1);
                 return -1;
             }
             break;
 
+        case 'E': /* --data-xpath */
+            if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) {
+                YLMSG_E("Storing XPath \"%s\" failed.\n", optarg);
+                return -1;
+            }
+            break;
+
         case 'l': /* --list */
-            c->list = 1;
+            yo->list = 1;
             break;
 
         case 'L': /* --tree-line-length */
-            c->line_length = atoi(optarg);
+            yo->line_length = atoi(optarg);
             break;
 
         case 'o': /* --output */
-            if (c->out) {
+            if (yo->out) {
                 YLMSG_E("Only a single output can be specified.\n");
                 return -1;
             } else {
-                if (ly_out_new_filepath(optarg, &c->out)) {
+                if (ly_out_new_filepath(optarg, &yo->out)) {
                     YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno));
                     return -1;
                 }
@@ -786,62 +625,45 @@
             break;
 
         case 'O': /* --operational */
-            if (c->data_operational.path) {
+            if (yo->data_operational.path) {
                 YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n");
                 return -1;
             }
-            c->data_operational.path = optarg;
+            yo->data_operational.path = optarg;
             break;
 
         case 'R': /* --reply-rpc */
-            if (c->reply_rpc.path) {
+            if (yo->reply_rpc.path) {
                 YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n");
                 return -1;
             }
-            c->reply_rpc.path = optarg;
+            yo->reply_rpc.path = optarg;
             break;
 
         case 'm': /* --merge */
-            c->data_merge = 1;
+            yo->data_merge = 1;
             break;
 
         case 'y': /* --yang-library */
-            c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+            yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
             break;
 
         case 'Y': /* --yang-library-file */
-            c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
-            c->yang_lib_file = optarg;
+            yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY;
+            yo->yang_lib_file = optarg;
+            break;
+
+        case 'X': /* --extended-leafref */
+            yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
             break;
 
 #ifndef NDEBUG
-        case 'G': { /* --debug */
-            uint32_t dbg_groups = 0;
-            const char *ptr = optarg;
-
-            while (ptr[0]) {
-                if (!strncasecmp(ptr, "dict", sizeof "dict" - 1)) {
-                    dbg_groups |= LY_LDGDICT;
-                    ptr += sizeof "dict" - 1;
-                } else if (!strncasecmp(ptr, "xpath", sizeof "xpath" - 1)) {
-                    dbg_groups |= LY_LDGXPATH;
-                    ptr += sizeof "xpath" - 1;
-                } else if (!strncasecmp(ptr, "dep-sets", sizeof "dep-sets" - 1)) {
-                    dbg_groups |= LY_LDGDEPSETS;
-                    ptr += sizeof "dep-sets" - 1;
-                }
-
-                if (ptr[0]) {
-                    if (ptr[0] != ',') {
-                        YLMSG_E("Unknown debug group string \"%s\"\n", optarg);
-                        return -1;
-                    }
-                    ++ptr;
-                }
+        case 'G':   /* --debug */
+            if (set_debug_groups(optarg, yo)) {
+                return -1;
             }
-            ly_log_dbg_groups(dbg_groups);
             break;
-        } /* case 'G' */
+            /* case 'G' */
 #endif
         default:
             YLMSG_E("Invalid option or missing argument: -%c\n", optopt);
@@ -850,86 +672,48 @@
     }
 
     /* additional checks for the options combinations */
-    if (!c->list && (optind >= argc)) {
+    if (!yo->list && (optind >= argc)) {
         help(1);
         YLMSG_E("Missing <schema> to process.\n");
         return 1;
     }
 
-    if (c->data_merge) {
-        if (c->data_type || (c->data_parse_options & LYD_PARSE_ONLY)) {
-            /* switch off the option, incompatible input data type */
-            c->data_merge = 0;
-        } else {
-            /* postpone validation after the merge of all the input data */
-            c->data_parse_options |= LYD_PARSE_ONLY;
-        }
+    if (cmd_data_dep(yo, 0)) {
+        return -1;
     }
-
-    if (c->data_operational.path && !c->data_type) {
-        YLMSG_E("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n");
-        c->data_operational.path = NULL;
-    }
-
-    if (c->reply_rpc.path && (c->data_type != LYD_TYPE_REPLY_NETCONF)) {
-        YLMSG_E("Source RPC is needed only for NETCONF Reply input data type.\n");
-        c->data_operational.path = NULL;
-    } else if (!c->reply_rpc.path && (c->data_type == LYD_TYPE_REPLY_NETCONF)) {
-        YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n");
+    if (cmd_print_dep(yo, 0)) {
         return -1;
     }
 
-    if ((c->schema_out_format != LYS_OUT_TREE) && c->line_length) {
-        YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n");
+    /* Create the libyang context. */
+    if (create_ly_context(yo->yang_lib_file, yo->searchpaths, &yo->schema_features, &yo->ctx_options, ctx)) {
+        return -1;
     }
 
-    /* default output stream */
-    if (!c->out) {
-        if (ly_out_new_file(stdout, &c->out)) {
-            YLMSG_E("Unable to set stdout as output.\n");
-            return -1;
-        }
+    /* Set callback providing run-time extension instance data. */
+    if (yo->schema_context_filename) {
+        ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, yo->schema_context_filename);
     }
 
-    if (c->schema_out_format == LYS_OUT_TREE) {
-        /* print tree from lysc_nodes */
-        c->ctx_options |= LY_CTX_SET_PRIV_PARSED;
-    }
-
-    /* process input files provided as standalone command line arguments,
-     * schema modules are parsed and inserted into the context,
-     * data files are just checked and prepared into internal structures for further processing */
-    ret = fill_context_inputs(argc, argv, c);
-    if (ret) {
-        return ret;
+    /* Schema modules and data files are just checked and prepared into internal structures for further processing. */
+    if (fill_context_inputs(argc, argv, optind, yo->data_in_format, *ctx, yo)) {
+        return -1;
     }
 
     /* the second batch of checks */
-    if (c->schema_print_options && !c->schema_out_format) {
+    if (yo->schema_print_options && !yo->schema_out_format) {
         YLMSG_W("Schema printer options specified, but the schema output format is missing.\n");
     }
-    if (c->schema_parse_options && !c->schema_modules.count) {
+    if (yo->schema_parse_options && !yo->schema_modules.count) {
         YLMSG_W("Schema parser options specified, but no schema input file provided.\n");
     }
-    if (c->data_print_options && !c->data_out_format) {
+    if (yo->data_print_options && !yo->data_out_format) {
         YLMSG_W("data printer options specified, but the data output format is missing.\n");
     }
-    if (((c->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || c->data_type) && !c->data_inputs.count) {
+    if (((yo->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || yo->data_type) && !yo->data_inputs.count) {
         YLMSG_W("Data parser options specified, but no data input file provided.\n");
     }
 
-    if (c->schema_node_path) {
-        c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 0);
-        if (!c->schema_node) {
-            c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 1);
-
-            if (!c->schema_node) {
-                YLMSG_E("Invalid schema path.\n");
-                return -1;
-            }
-        }
-    }
-
     return 0;
 }
 
@@ -937,7 +721,8 @@
 main_ni(int argc, char *argv[])
 {
     int ret = EXIT_SUCCESS, r;
-    struct context c = {0};
+    struct yl_opt yo = {0};
+    struct ly_ctx *ctx = NULL;
     char *features_output = NULL;
     struct ly_set set = {0};
     uint32_t u;
@@ -945,7 +730,7 @@
     /* set callback for printing libyang messages */
     ly_set_log_clb(libyang_verbclb, 1);
 
-    r = fill_context(argc, argv, &c);
+    r = fill_context(argc, argv, &yo, &ctx);
     if (r < 0) {
         ret = EXIT_FAILURE;
     }
@@ -955,72 +740,46 @@
 
     /* do the required job - parse, validate, print */
 
-    if (c.list) {
+    if (yo.list) {
         /* print the list of schemas */
-        print_list(c.out, c.ctx, c.data_out_format);
-    } else {
-        if (c.feature_param_format) {
-            for (u = 0; u < c.schema_modules.count; u++) {
-                if (collect_features(c.schema_modules.objs[u], &set)) {
-                    YLMSG_E("Unable to read features from a module.\n");
-                    goto cleanup;
-                }
-                if (generate_features_output(c.schema_modules.objs[u], &set, &features_output)) {
-                    YLMSG_E("Unable to generate feature command output.\n");
-                    goto cleanup;
-                }
-                ly_set_erase(&set, NULL);
-            }
-            ly_print(c.out, "%s\n", features_output);
-        } else if (c.schema_out_format) {
-            if (c.schema_node) {
-                ret = lys_print_node(c.out, c.schema_node, c.schema_out_format, 0, c.schema_print_options);
-                if (ret) {
-                    YLMSG_E("Unable to print schema node %s.\n", c.schema_node_path);
-                    goto cleanup;
-                }
-            } else if (c.submodule) {
-                const struct lysp_submodule *submod = ly_ctx_get_submodule_latest(c.ctx, c.submodule);
-
-                if (!submod) {
-                    YLMSG_E("Unable to find submodule %s.\n", c.submodule);
-                    goto cleanup;
-                }
-
-                ret = lys_print_submodule(c.out, submod, c.schema_out_format, c.line_length, c.schema_print_options);
-                if (ret) {
-                    YLMSG_E("Unable to print submodule %s.\n", submod->name);
-                    goto cleanup;
-                }
-            } else {
-                for (u = 0; u < c.schema_modules.count; ++u) {
-                    ret = lys_print_module(c.out, (struct lys_module *)c.schema_modules.objs[u], c.schema_out_format,
-                            c.line_length, c.schema_print_options);
-                    /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */
-                    if ((c.schema_out_format == LYS_OUT_TREE) && (u + 1 < c.schema_modules.count)) {
-                        ly_print(c.out, "\n");
-                    }
-                    if (ret) {
-                        YLMSG_E("Unable to print module %s.\n", ((struct lys_module *)c.schema_modules.objs[u])->name);
-                        goto cleanup;
-                    }
-                }
+        ret = cmd_list_exec(&ctx, &yo, NULL);
+        goto cleanup;
+    }
+    if (yo.feature_param_format) {
+        for (u = 0; u < yo.schema_modules.count; u++) {
+            if ((ret = cmd_feature_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) {
+                goto cleanup;
             }
         }
-
-        /* do the data validation despite the schema was printed */
-        if (c.data_inputs.size) {
-            ret = process_data(c.ctx, c.data_type, c.data_merge, c.data_out_format, c.out, c.data_parse_options,
-                    c.data_validate_options, c.data_print_options, &c.data_operational, &c.reply_rpc, &c.data_inputs, NULL);
-            if (ret) {
+        cmd_feature_fin(ctx, &yo);
+    } else if (yo.schema_out_format && yo.schema_node_path) {
+        if ((ret = cmd_print_exec(&ctx, &yo, NULL))) {
+            goto cleanup;
+        }
+    } else if (yo.schema_out_format && yo.submodule) {
+        if ((ret = cmd_print_exec(&ctx, &yo, yo.submodule))) {
+            goto cleanup;
+        }
+    } else if (yo.schema_out_format) {
+        for (u = 0; u < yo.schema_modules.count; ++u) {
+            yo.last_one = (u + 1) == yo.schema_modules.count;
+            if ((ret = cmd_print_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) {
                 goto cleanup;
             }
         }
     }
 
+    /* do the data validation despite the schema was printed */
+    if (yo.data_inputs.size) {
+        if ((ret = cmd_data_process(ctx, &yo))) {
+            goto cleanup;
+        }
+    }
+
 cleanup:
     /* cleanup */
-    erase_context(&c);
+    yl_opt_erase(&yo);
+    ly_ctx_destroy(ctx);
     free(features_output);
     ly_set_erase(&set, NULL);
 
diff --git a/tools/lint/tests/expect/common.exp b/tools/lint/tests/expect/common.exp
deleted file mode 100644
index 0381e6c..0000000
--- a/tools/lint/tests/expect/common.exp
+++ /dev/null
@@ -1,68 +0,0 @@
-# detect the path to the yanglint binary
-if { [info exists ::env(YANGLINT)] } {
-    set yanglint "$env(YANGLINT)"
-} else {
-    set yanglint "../../../../build/yanglint"
-}
-
-# set the timeout to 1 second
-set timeout 1
-
-# expect a single line of anchored regex output
-proc expect_output {output} {
-    expect {
-        -re "^${output}$" {}
-        timeout {exit 1}
-    }
-}
-
-# send a command and either expect some anchored regex output if specified or just an empty line
-proc expect_command {command has_output output} {
-    send -- "${command}\r"
-
-    if ($has_output==1) {
-        expect {
-            -re "^${command}\r\n${output}$" {}
-            timeout {exit 1}
-        }
-    } else {
-        # input echoes
-        expect {
-            -re "^${command}\r\n$" {}
-            timeout {exit 1}
-        }
-        expect {
-            -re "^> $" {}
-            timeout {exit 1}
-        }
-    }
-}
-
-# send a completion request and check if the anchored regex output matches
-proc expect_completion {input output} {
-    send -- "${input}\t"
-
-    expect {
-        # expecting echoing input, output and 10 terminal control characters
-        -re "^${input}\r> ${output}.*\r.*$" {}
-        timeout {exit 1}
-    }
-}
-
-# send a completion request and check if the anchored regex hint options match
-proc expect_hint {input prev_input hints} {
-    set output {}
-    foreach i $hints {
-        # each element might have some number of spaces and CRLF around it
-        append output "${i} *(?:\\r\\n)?"
-    }
-
-    send -- "${input}\t"
-
-    expect {
-        # expecting the hints, previous input from which the hints were generated
-        # and some number of terminal control characters
-        -re "^\r\n${output}\r> ${prev_input}.*\r.*$" {}
-        timeout {exit 1}
-    }
-}
diff --git a/tools/lint/tests/expect/completion.exp b/tools/lint/tests/expect/completion.exp
deleted file mode 100755
index ed4f6bd..0000000
--- a/tools/lint/tests/expect/completion.exp
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/expect -f
-
-if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
-    source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
-    set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
-} else {
-    source "common.exp"
-    set yang_dir "../../examples"
-}
-
-spawn $yanglint
-
-# skip no dir and/or no history warnings
-expect_output "(YANGLINT.*)*> "
-
-expect_command "clear -ii" 0 ""
-
-expect_command "add ${yang_dir}/ietf-ip.yang" 0 ""
-
-expect_completion "print -f info -P " "print -f info -P /ietf-"
-
-set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"}
-
-expect_hint "" "print -f info -P /ietf-" $hints
-
-expect_completion "i" "print -f info -P /ietf-interfaces:interfaces"
-
-expect_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface"
-
-set hints {"/ietf-interfaces:interfaces/interface"
-"/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description"
-"/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled"
-"/ietf-interfaces:interfaces/interface/link-up-down-trap-enable"
-"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"}
-
-expect_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints
-
-expect_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv"
-
-expect_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4"
-
-set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled"
-"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu"
-"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor"
-}
-
-expect_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints
-
-expect_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled "
-
-send -- "\r"
-
-expect {
-    -re ".*\r\n> " {}
-    timeout {exit 1}
-}
-
-send -- "exit\r"
-
-expect eof
diff --git a/tools/lint/tests/expect/feature.exp b/tools/lint/tests/expect/feature.exp
deleted file mode 100755
index 37680b0..0000000
--- a/tools/lint/tests/expect/feature.exp
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/expect -f
-
-if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
-    source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
-    set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
-} else {
-    source "common.exp"
-    set yang_dir "../../examples"
-}
-
-spawn $yanglint
-
-# skip no dir and/or no history warnings
-expect_output "(YANGLINT.*)*> "
-
-expect_command "feature -a" 1 "yang:\r\n\t\\(none\\)\r\n\r\nietf-yang-schema-mount:\r\n\t\\(none\\)\r\n\r\n> "
-
-send -- "exit\r"
-
-expect eof
diff --git a/tools/lint/tests/expect/list.exp b/tools/lint/tests/expect/list.exp
deleted file mode 100755
index ec3cdba..0000000
--- a/tools/lint/tests/expect/list.exp
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/expect -f
-
-if { [info exists ::env(CURRENT_SOURCE_DIR)] } {
-    source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp"
-    set yang_dir "$env(CURRENT_SOURCE_DIR)/examples"
-} else {
-    source "common.exp"
-    set yang_dir "../../examples"
-}
-
-spawn $yanglint
-
-# skip no dir and/or no history warnings
-expect_output "(YANGLINT.*)*> "
-
-expect_command "list" 1 "List of the loaded models:\r\n *i ietf-yang-metadata@2016-08-05\r\n *I yang@2022-06-16\r\n *i ietf-inet-types@2013-07-15\r\n *i ietf-yang-types@2013-07-15\r\n *I ietf-yang-schema-mount@2019-01-14\r\n *i ietf-yang-structure-ext@2020-06-17\r\n> "
-
-send -- "exit\r"
-
-expect eof
diff --git a/tools/lint/tests/shunit2/feature.sh b/tools/lint/tests/shunit2/feature.sh
deleted file mode 100755
index fb2ee88..0000000
--- a/tools/lint/tests/shunit2/feature.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-testFeature() {
-	models=( "iana-if-type@2014-05-08.yang" "ietf-netconf@2011-06-01.yang" "ietf-netconf-with-defaults@2011-06-01.yang"
-	 		"sm.yang" "ietf-interfaces@2014-05-08.yang" "ietf-netconf-acm@2018-02-14.yang" "ietf-origin@2018-02-14.yang"
-	 		"ietf-ip@2014-06-16.yang" "ietf-restconf@2017-01-26.yang" )
-	features=( " -F iana-if-type:"
-			  " -F ietf-netconf:writable-running,candidate,confirmed-commit,rollback-on-error,validate,startup,url,xpath"
-			  " -F ietf-netconf-with-defaults:" " -F sm:" " -F ietf-interfaces:arbitrary-names,pre-provisioning,if-mib"
-			  " -F ietf-netconf-acm:" " -F ietf-origin:" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf"
-			  " -F ietf-restconf:" )
-
-	for i in ${!models[@]}; do
-		output=`${YANGLINT} -f feature-param ${YANG_MODULES_DIR}/${models[$i]}`
-		assertEquals "Unexpected features of module ${models[$i]}." "${features[$i]}" "${output}"
-	done
-}
-
-. shunit2
diff --git a/tools/lint/tests/shunit2/list.sh b/tools/lint/tests/shunit2/list.sh
deleted file mode 100755
index d64503a..0000000
--- a/tools/lint/tests/shunit2/list.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env bash
-
-LIST_BASE="List of the loaded models:
-    i ietf-yang-metadata@2016-08-05
-    I yang@2022-06-16
-    i ietf-inet-types@2013-07-15
-    i ietf-yang-types@2013-07-15
-    I ietf-yang-schema-mount@2019-01-14
-    i ietf-yang-structure-ext@2020-06-17"
-
-testListEmptyContext() {
-  output=`${YANGLINT} -l`
-  assertEquals "Unexpected list of modules in empty context." "${LIST_BASE}" "${output}"
-}
-
-testListAllImplemented() {
-  LIST_BASE_ALLIMPLEMENTED="List of the loaded models:
-    I ietf-yang-metadata@2016-08-05
-    I yang@2022-06-16
-    I ietf-inet-types@2013-07-15
-    I ietf-yang-types@2013-07-15
-    I ietf-yang-schema-mount@2019-01-14
-    I ietf-yang-structure-ext@2020-06-17"
-  output=`${YANGLINT} -lii`
-  assertEquals "Unexpected list of modules in empty context with -ii." "${LIST_BASE_ALLIMPLEMENTED}" "${output}"
-}
-
-. shunit2
diff --git a/tools/lint/yl_opt.c b/tools/lint/yl_opt.c
new file mode 100644
index 0000000..1a6add0
--- /dev/null
+++ b/tools/lint/yl_opt.c
@@ -0,0 +1,344 @@
+/**
+ * @file yl_opt.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Settings options for the libyang context.
+ *
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <strings.h>
+
+#include "in.h" /* ly_in_free */
+
+#include "common.h"
+#include "yl_opt.h"
+#include "yl_schema_features.h"
+
+struct cmdline_file *
+fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
+{
+    struct cmdline_file *rec;
+
+    rec = malloc(sizeof *rec);
+    if (!rec) {
+        YLMSG_E("Allocating memory for data file information failed.\n");
+        return NULL;
+    }
+    rec->in = in;
+    rec->path = path;
+    rec->format = format;
+
+    if (set && ly_set_add(set, rec, 1, NULL)) {
+        free(rec);
+        YLMSG_E("Storing data file information failed.\n");
+        return NULL;
+    }
+
+    return rec;
+}
+
+void
+free_cmdline_file_items(struct cmdline_file *rec)
+{
+    if (rec && rec->in) {
+        ly_in_free(rec->in, 1);
+    }
+}
+
+void
+free_cmdline_file(void *cmdline_file)
+{
+    struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
+
+    if (rec) {
+        free_cmdline_file_items(rec);
+        free(rec);
+    }
+}
+
+void
+yl_opt_erase(struct yl_opt *yo)
+{
+    ly_bool interactive;
+
+    interactive = yo->interactive;
+
+    /* data */
+    ly_set_erase(&yo->data_inputs, free_cmdline_file);
+    ly_in_free(yo->data_operational.in, 1);
+    ly_set_erase(&yo->data_xpath, NULL);
+
+    /* schema */
+    ly_set_erase(&yo->schema_features, yl_schema_features_free);
+    ly_set_erase(&yo->schema_modules, NULL);
+
+    /* context */
+    free(yo->searchpaths);
+
+    /* --reply-rpc */
+    ly_in_free(yo->reply_rpc.in, 1);
+
+    ly_out_free(yo->out, NULL, yo->out_stdout ? 0 : 1);
+
+    free_cmdline(yo->argv);
+
+    *yo = (const struct yl_opt) {
+        0
+    };
+    yo->interactive = interactive;
+}
+
+int
+yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo)
+{
+    if (!strcasecmp(arg, "yang")) {
+        yo->schema_out_format = LYS_OUT_YANG;
+        yo->data_out_format = 0;
+    } else if (!strcasecmp(arg, "yin")) {
+        yo->schema_out_format = LYS_OUT_YIN;
+        yo->data_out_format = 0;
+    } else if (!strcasecmp(arg, "info")) {
+        yo->schema_out_format = LYS_OUT_YANG_COMPILED;
+        yo->data_out_format = 0;
+    } else if (!strcasecmp(arg, "tree")) {
+        yo->schema_out_format = LYS_OUT_TREE;
+        yo->data_out_format = 0;
+    } else {
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo)
+{
+    if (!strcasecmp(arg, "xml")) {
+        yo->schema_out_format = 0;
+        yo->data_out_format = LYD_XML;
+    } else if (!strcasecmp(arg, "json")) {
+        yo->schema_out_format = 0;
+        yo->data_out_format = LYD_JSON;
+    } else if (!strcasecmp(arg, "lyb")) {
+        yo->schema_out_format = 0;
+        yo->data_out_format = LYD_LYB;
+    } else {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int
+yl_opt_update_other_out_format(const char *arg, struct yl_opt *yo)
+{
+    if (!strcasecmp(arg, "feature-param")) {
+        yo->feature_param_format = 1;
+    } else {
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+yl_opt_update_out_format(const char *arg, struct yl_opt *yo)
+{
+    if (!yl_opt_update_schema_out_format(arg, yo)) {
+        return 0;
+    }
+    if (!yl_opt_update_data_out_format(arg, yo)) {
+        return 0;
+    }
+    if (!yl_opt_update_other_out_format(arg, yo)) {
+        return 0;
+    }
+
+    YLMSG_E("Unknown output format %s\n", arg);
+    return 1;
+}
+
+int
+yl_opt_update_data_type(const char *arg, struct yl_opt *yo)
+{
+    if (!strcasecmp(arg, "config")) {
+        yo->data_parse_options |= LYD_PARSE_NO_STATE;
+        yo->data_validate_options |= LYD_VALIDATE_NO_STATE;
+    } else if (!strcasecmp(arg, "get")) {
+        yo->data_parse_options |= LYD_PARSE_ONLY;
+    } else if (!strcasecmp(arg, "getconfig") || !strcasecmp(arg, "get-config") || !strcasecmp(arg, "edit")) {
+        yo->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE;
+    } else if (!strcasecmp(arg, "rpc") || !strcasecmp(arg, "action")) {
+        yo->data_type = LYD_TYPE_RPC_YANG;
+    } else if (!strcasecmp(arg, "nc-rpc")) {
+        yo->data_type = LYD_TYPE_RPC_NETCONF;
+    } else if (!strcasecmp(arg, "reply") || !strcasecmp(arg, "rpcreply")) {
+        yo->data_type = LYD_TYPE_REPLY_YANG;
+    } else if (!strcasecmp(arg, "nc-reply")) {
+        yo->data_type = LYD_TYPE_REPLY_NETCONF;
+    } else if (!strcasecmp(arg, "notif") || !strcasecmp(arg, "notification")) {
+        yo->data_type = LYD_TYPE_NOTIF_YANG;
+    } else if (!strcasecmp(arg, "nc-notif")) {
+        yo->data_type = LYD_TYPE_NOTIF_NETCONF;
+    } else if (!strcasecmp(arg, "data")) {
+        /* default option */
+    } else {
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+yo_opt_update_data_default(const char *arg, struct yl_opt *yo)
+{
+    if (!strcasecmp(arg, "all")) {
+        yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL;
+    } else if (!strcasecmp(arg, "all-tagged")) {
+        yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG;
+    } else if (!strcasecmp(arg, "trim")) {
+        yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM;
+    } else if (!strcasecmp(arg, "implicit-tagged")) {
+        yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG;
+    } else {
+        return 1;
+    }
+
+    return 0;
+}
+
+int
+yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo)
+{
+    if (!strcasecmp(arg, "xml")) {
+        yo->data_in_format = LYD_XML;
+    } else if (!strcasecmp(arg, "json")) {
+        yo->data_in_format = LYD_JSON;
+    } else if (!strcasecmp(arg, "lyb")) {
+        yo->data_in_format = LYD_LYB;
+    } else {
+        return 1;
+    }
+
+    return 0;
+}
+
+void
+yo_opt_update_make_implemented(struct yl_opt *yo)
+{
+    if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) {
+        yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED;
+        yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED;
+    } else {
+        yo->ctx_options |= LY_CTX_REF_IMPLEMENTED;
+    }
+}
+
+void
+yo_opt_update_disable_searchdir(struct yl_opt *yo)
+{
+    if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) {
+        yo->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
+        yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS;
+    } else {
+        yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD;
+    }
+}
+
+void
+free_cmdline(char *argv[])
+{
+    if (argv) {
+        free(argv[0]);
+        free(argv);
+    }
+}
+
+int
+parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
+{
+    int count;
+    char **vector;
+    char *ptr;
+    char qmark = 0;
+
+    assert(cmdline);
+    assert(argc_p);
+    assert(argv_p);
+
+    /* init */
+    optind = 0; /* reinitialize getopt() */
+    count = 1;
+    vector = malloc((count + 1) * sizeof *vector);
+    vector[0] = strdup(cmdline);
+
+    /* command name */
+    strtok(vector[0], " ");
+
+    /* arguments */
+    while ((ptr = strtok(NULL, " "))) {
+        size_t len;
+        void *r;
+
+        len = strlen(ptr);
+
+        if (qmark) {
+            /* still in quotated text */
+            /* remove NULL termination of the previous token since it is not a token,
+             * but a part of the quotation string */
+            ptr[-1] = ' ';
+
+            if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
+                /* end of quotation */
+                qmark = 0;
+                /* shorten the argument by the terminating quotation mark */
+                ptr[len - 1] = '\0';
+            }
+            continue;
+        }
+
+        /* another token in cmdline */
+        ++count;
+        r = realloc(vector, (count + 1) * sizeof *vector);
+        if (!r) {
+            YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
+            free(vector);
+            return -1;
+        }
+        vector = r;
+        vector[count - 1] = ptr;
+
+        if ((ptr[0] == '"') || (ptr[0] == '\'')) {
+            /* remember the quotation mark to identify end of quotation */
+            qmark = ptr[0];
+
+            /* move the remembered argument after the quotation mark */
+            ++vector[count - 1];
+
+            /* check if the quotation is terminated within this token */
+            if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
+                /* end of quotation */
+                qmark = 0;
+                /* shorten the argument by the terminating quotation mark */
+                ptr[len - 1] = '\0';
+            }
+        }
+    }
+    vector[count] = NULL;
+
+    *argc_p = count;
+    *argv_p = vector;
+
+    return 0;
+}
diff --git a/tools/lint/yl_opt.h b/tools/lint/yl_opt.h
new file mode 100644
index 0000000..d66ae4d
--- /dev/null
+++ b/tools/lint/yl_opt.h
@@ -0,0 +1,237 @@
+/**
+ * @file yl_opt.h
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Settings options for the libyang context.
+ *
+ * Copyright (c) 2020 - 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef YL_OPT_H_
+#define YL_OPT_H_
+
+#include "parser_data.h" /* enum lyd_type */
+#include "printer_schema.h" /* LYS_OUTFORMAT */
+#include "set.h" /* ly_set */
+
+/**
+ * @brief Data connected with a file provided on a command line as a file path.
+ */
+struct cmdline_file {
+    struct ly_in *in;
+    const char *path;
+    LYD_FORMAT format;
+};
+
+/**
+ * @brief Create and fill the command line file data (struct cmdline_file *).
+ * @param[in] set Optional parameter in case the record is supposed to be added into a set.
+ * @param[in] in Input file handler.
+ * @param[in] path Filepath of the file.
+ * @param[in] format Format of the data file.
+ * @return The created command line file structure.
+ * @return NULL on failure
+ */
+struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format);
+
+/**
+ * @brief Free the command line file data items.
+ * @param[in,out] rec record to free.
+ */
+void free_cmdline_file_items(struct cmdline_file *rec);
+
+/**
+ * @brief Free the command line file data (struct cmdline_file *).
+ * @param[in,out] cmdline_file The (struct cmdline_file *) to free.
+ */
+void free_cmdline_file(void *cmdline_file);
+
+/**
+ * @brief Context structure to hold and pass variables in a structured form.
+ */
+struct yl_opt {
+    /* Set to 1 if yanglint running in the interactive mode */
+    ly_bool interactive;
+    ly_bool last_one;
+
+    /* libyang context for the run */
+    char *yang_lib_file;
+    uint16_t ctx_options;
+
+    /* prepared output (--output option or stdout by default) */
+    ly_bool out_stdout;
+    struct ly_out *out;
+
+    char *searchpaths;
+    ly_bool searchdir_unset;
+
+    /* options flags */
+    uint8_t list;        /* -l option to print list of schemas */
+
+    /* line length for 'tree' format */
+    size_t line_length; /* --tree-line-length */
+
+    uint32_t dbg_groups;
+
+    /*
+     * schema
+     */
+    /* set schema modules' features via --features option (struct schema_features *) */
+    struct ly_set schema_features;
+
+    /* set of loaded schema modules (struct lys_module *) */
+    struct ly_set schema_modules;
+
+    /* options to parse and print schema modules */
+    uint32_t schema_parse_options;
+    uint32_t schema_print_options;
+
+    /* specification of printing schema node subtree, option --schema-node */
+    char *schema_node_path;
+    char *submodule;
+
+    /* name of file containing explicit context passed to callback
+     * for schema-mount extension.  This also causes a callback to
+     * be registered.
+     */
+    char *schema_context_filename;
+    ly_bool extdata_unset;
+
+    /* value of --format in case of schema format */
+    LYS_OUTFORMAT schema_out_format;
+    ly_bool feature_param_format;
+    ly_bool feature_print_all;
+
+    /*
+     * data
+     */
+    /* various options based on --type option */
+    enum lyd_type data_type;
+    uint32_t data_parse_options;
+    uint32_t data_validate_options;
+    uint32_t data_print_options;
+
+    /* flag for --merge option */
+    uint8_t data_merge;
+
+    /* value of --format in case of data format */
+    LYD_FORMAT data_out_format;
+
+    /* value of --in-format in case of data format */
+    LYD_FORMAT data_in_format;
+
+    /* input data files (struct cmdline_file *) */
+    struct ly_set data_inputs;
+
+    /* storage for --operational */
+    struct cmdline_file data_operational;
+
+    /* storage for --reply-rpc */
+    struct cmdline_file reply_rpc;
+
+    /* storage for --data-xpath */
+    struct ly_set data_xpath;
+
+    char **argv;
+};
+
+/**
+ * @brief Erase all values in @p opt.
+ *
+ * The yl_opt.interactive item is not deleted.
+ *
+ * @param[in,out] yo Option context to erase.
+ */
+void yl_opt_erase(struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the schema --format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example yang, yin, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example xml, json, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the general --format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example yang, xml, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_out_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --type parameter.
+ *
+ * @param[in] arg Format parameter argument (for example config, rpc, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yl_opt_update_data_type(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --default parameter.
+ *
+ * @param[in] arg Format parameter argument (for example all, trim, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yo_opt_update_data_default(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the @p arg of the data --in-format parameter.
+ *
+ * @param[in] arg Format parameter argument (for example xml, json, ...).
+ * @param[out] yo yanglint options used to update.
+ * @return 0 on success.
+ */
+int yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the --make-implemented parameter.
+ *
+ * @param[in,out] yo yanglint options used to update.
+ */
+void yo_opt_update_make_implemented(struct yl_opt *yo);
+
+/**
+ * @brief Update @p yo according to the --disable-searchdir parameter.
+ *
+ * @param[in,out] yo yanglint options used to update.
+ */
+void yo_opt_update_disable_searchdir(struct yl_opt *yo);
+
+/**
+ * @brief Helper function to prepare argc, argv pair from a command line string.
+ *
+ * @param[in] cmdline Complete command line string.
+ * @param[out] argc_p Pointer to store argc value.
+ * @param[out] argv_p Pointer to store argv vector.
+ * @return 0 on success, non-zero on failure.
+ */
+int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]);
+
+/**
+ * @brief Destructor for the argument vector prepared by ::parse_cmdline().
+ *
+ * @param[in,out] argv Argument vector to destroy.
+ */
+void free_cmdline(char *argv[]);
+
+#endif /* YL_OPT_H_ */
diff --git a/tools/lint/yl_schema_features.c b/tools/lint/yl_schema_features.c
new file mode 100644
index 0000000..ceaff36
--- /dev/null
+++ b/tools/lint/yl_schema_features.c
@@ -0,0 +1,212 @@
+/**
+ * @file yl_schema_features.c
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Control features for the schema.
+ *
+ * Copyright (c) 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdlib.h> /* calloc */
+#include <string.h> /* strcmp */
+
+#include "compat.h" /* strndup */
+#include "set.h" /* ly_set */
+
+#include "common.h"
+#include "yl_schema_features.h"
+
+void
+yl_schema_features_free(void *flist)
+{
+    struct yl_schema_features *rec = (struct yl_schema_features *)flist;
+
+    if (rec) {
+        free(rec->mod_name);
+        if (rec->features) {
+            for (uint32_t u = 0; rec->features[u]; ++u) {
+                free(rec->features[u]);
+            }
+            free(rec->features);
+        }
+        free(rec);
+    }
+}
+
+void
+get_features(const struct ly_set *fset, const char *module, const char ***features)
+{
+    /* get features list for this module */
+    for (uint32_t u = 0; u < fset->count; ++u) {
+        struct yl_schema_features *sf = (struct yl_schema_features *)fset->objs[u];
+
+        if (!strcmp(module, sf->mod_name)) {
+            /* matched module - explicitly set features */
+            *features = (const char **)sf->features;
+            sf->applied = 1;
+            return;
+        }
+    }
+
+    /* features not set so disable all */
+    *features = NULL;
+}
+
+int
+parse_features(const char *fstring, struct ly_set *fset)
+{
+    struct yl_schema_features *rec = NULL;
+    uint32_t count;
+    char *p, **fp;
+
+    rec = calloc(1, sizeof *rec);
+    if (!rec) {
+        YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
+        goto error;
+    }
+
+    /* fill the record */
+    p = strchr(fstring, ':');
+    if (!p) {
+        YLMSG_E("Invalid format of the features specification (%s).\n", fstring);
+        goto error;
+    }
+    rec->mod_name = strndup(fstring, p - fstring);
+
+    count = 0;
+    while (p) {
+        size_t len = 0;
+        char *token = p + 1;
+
+        p = strchr(token, ',');
+        if (!p) {
+            /* the last item, if any */
+            len = strlen(token);
+        } else {
+            len = p - token;
+        }
+
+        if (len) {
+            fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
+            if (!fp) {
+                YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
+                goto error;
+            }
+            rec->features = fp;
+            fp = &rec->features[count++]; /* array item to set */
+            (*fp) = strndup(token, len);
+        }
+    }
+
+    /* terminating NULL */
+    fp = realloc(rec->features, (count + 1) * sizeof *rec->features);
+    if (!fp) {
+        YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
+        goto error;
+    }
+    rec->features = fp;
+    rec->features[count++] = NULL;
+
+    /* Store record to the output set. */
+    if (ly_set_add(fset, rec, 1, NULL)) {
+        YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
+        goto error;
+    }
+    rec = NULL;
+
+    return 0;
+
+error:
+    yl_schema_features_free(rec);
+    return -1;
+}
+
+void
+print_features(struct ly_out *out, const struct lys_module *mod)
+{
+    struct lysp_feature *f;
+    uint32_t idx;
+    size_t max_len, len;
+
+    ly_print(out, "%s:\n", mod->name);
+
+    /* get max len, so the statuses of all the features will be aligned */
+    max_len = 0, idx = 0, f = NULL;
+    while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+        len = strlen(f->name);
+        max_len = (max_len > len) ? max_len : len;
+    }
+    if (!max_len) {
+        ly_print(out, "\t(none)\n");
+        return;
+    }
+
+    /* print features */
+    idx = 0, f = NULL;
+    while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+        ly_print(out, "\t%-*s (%s)\n", (int)max_len, f->name, lys_feature_value(mod, f->name) ? "off" : "on");
+    }
+}
+
+void
+print_feature_param(struct ly_out *out, const struct lys_module *mod)
+{
+    struct lysp_feature *f = NULL;
+    uint32_t idx = 0;
+    uint8_t first = 1;
+
+    ly_print(out, " -F %s:", mod->name);
+    while ((f = lysp_feature_next(f, mod->parsed, &idx))) {
+        if (first) {
+            ly_print(out, "%s", f->name);
+            first = 0;
+        } else {
+            ly_print(out, ",%s", f->name);
+        }
+    }
+}
+
+void
+print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param)
+{
+    uint32_t i;
+    struct lys_module *mod;
+    uint8_t first;
+
+    /* Print features for all implemented modules. */
+    first = 1;
+    i = 0;
+    while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
+        if (!mod->implemented) {
+            continue;
+        }
+        if (first) {
+            print_features(out, mod);
+            first = 0;
+        } else {
+            ly_print(out, "\n");
+            print_features(out, mod);
+        }
+    }
+
+    if (!feature_param) {
+        return;
+    }
+    ly_print(out, "\n");
+
+    /* Print features for all implemented modules in 'feature-param' format. */
+    i = 0;
+    while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) {
+        if (mod->implemented) {
+            print_feature_param(out, mod);
+        }
+    }
+}
diff --git a/tools/lint/yl_schema_features.h b/tools/lint/yl_schema_features.h
new file mode 100644
index 0000000..7bfe9fd
--- /dev/null
+++ b/tools/lint/yl_schema_features.h
@@ -0,0 +1,84 @@
+/**
+ * @file yl_schema_features.h
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief Control features for the schema.
+ *
+ * Copyright (c) 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef YL_SCHEMA_FEATURES_H_
+#define YL_SCHEMA_FEATURES_H_
+
+#include <stdint.h>
+
+struct ly_set;
+struct lys_module;
+struct ly_out;
+struct ly_ctx;
+
+/**
+ * @brief Storage for the list of the features (their names) in a specific YANG module.
+ */
+struct yl_schema_features {
+    char *mod_name;
+    char **features;
+    uint8_t applied;
+};
+
+/**
+ * @brief Free the schema features list (struct schema_features *)
+ * @param[in,out] flist The (struct schema_features *) to free.
+ */
+void yl_schema_features_free(void *flist);
+
+/**
+ * @brief Get the list of features connected with the specific YANG module.
+ *
+ * @param[in] fset The set of features information (struct schema_features *).
+ * @param[in] module Name of the YANG module which features should be found.
+ * @param[out] features Pointer to the list of features being returned.
+ */
+void get_features(const struct ly_set *fset, const char *module, const char ***features);
+
+/**
+ * @brief Parse features being specified for the specific YANG module.
+ *
+ * Format of the input @p fstring is as follows: "<module_name>:[<feature>,]*"
+ *
+ * @param[in] fstring Input string to be parsed.
+ * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled.
+ */
+int parse_features(const char *fstring, struct ly_set *fset);
+
+/**
+ * @brief Print all features of a single module.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] mod Module which can contains the features.
+ */
+void print_features(struct ly_out *out, const struct lys_module *mod);
+
+/**
+ * @brief Print all features in the 'feature-param' format.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] mod Module which can contains the features.
+ */
+void print_feature_param(struct ly_out *out, const struct lys_module *mod);
+
+/**
+ * @brief Print all features of all implemented modules.
+ *
+ * @param[in] out The output handler for printing.
+ * @param[in] ctx Libyang context.
+ * @param[in] feature_param Flag expressing whether to print features parameter.
+ */
+void print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param);
+
+#endif /* YL_SCHEMA_FEATURES_H_ */
diff --git a/tools/re/main.c b/tools/re/main.c
index 2292b2a..5e33536 100644
--- a/tools/re/main.c
+++ b/tools/re/main.c
@@ -1,6 +1,7 @@
 /**
  * @file main.c
  * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
  * @brief libyang's YANG Regular Expression tool
  *
  * Copyright (c) 2017 CESNET, z.s.p.o.
@@ -26,6 +27,11 @@
 #include "compat.h"
 #include "tools/config.h"
 
+struct yr_pattern {
+    char *expr;
+    ly_bool invert;
+};
+
 void
 help(void)
 {
@@ -34,7 +40,9 @@
     fprintf(stdout, "    yangre [-hv]\n");
     fprintf(stdout, "    yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
     fprintf(stdout, "    yangre [-V] -f <file>\n");
-    fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n");
+    fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n");
+    fprintf(stdout, "Returns 1 on error.\n");
+    fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n");
     fprintf(stdout, "Options:\n"
             "  -h, --help              Show this help message and exit.\n"
             "  -v, --version           Show version number and exit.\n"
@@ -74,43 +82,248 @@
     }
 }
 
-static const char *module_start = "module yangre {"
-        "yang-version 1.1;"
-        "namespace urn:cesnet:libyang:yangre;"
-        "prefix re;"
-        "leaf pattern {"
-        "  type string {";
-static const char *module_invertmatch = " { modifier invert-match; }";
-static const char *module_match = ";";
-static const char *module_end = "}}}";
+static int
+add_pattern(struct yr_pattern **patterns, int *counter, char *pattern)
+{
+    void *reallocated;
+    int orig_counter;
+
+    /* Store the original number of items. */
+    orig_counter = *counter;
+
+    /* Reallocate 'patterns' memory with additional space. */
+    reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
+    if (!reallocated) {
+        goto error;
+    }
+    (*patterns) = reallocated;
+    /* Allocated memory is now larger. */
+    (*counter)++;
+    /* Copy the pattern and store it to the additonal space. */
+    (*patterns)[orig_counter].expr = strdup(pattern);
+    if (!(*patterns)[orig_counter].expr) {
+        goto error;
+    }
+    (*patterns)[orig_counter].invert = 0;
+
+    return 0;
+
+error:
+    fprintf(stderr, "yangre error: memory allocation error.\n");
+    return 1;
+}
 
 static int
-add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
+create_empty_string(char **str)
 {
-    void *reallocated1, *reallocated2;
-
-    (*counter)++;
-    reallocated1 = realloc(*patterns, *counter * sizeof **patterns);
-    reallocated2 = realloc(*inverts, *counter * sizeof **inverts);
-    if (!reallocated1 || !reallocated2) {
-        fprintf(stderr, "yangre error: memory allocation error.\n");
-        free(reallocated1);
-        free(reallocated2);
-        return EXIT_FAILURE;
+    free(*str);
+    *str = malloc(sizeof(char));
+    if (!(*str)) {
+        fprintf(stderr, "yangre error: memory allocation failed.\n");
+        return 1;
     }
-    (*patterns) = reallocated1;
-    (*patterns)[*counter - 1] = strdup(pattern);
-    (*inverts) = reallocated2;
-    (*inverts)[*counter - 1] = 0;
+    (*str)[0] = '\0';
 
-    return EXIT_SUCCESS;
+    return 0;
+}
+
+static ly_bool
+file_is_empty(FILE *fp)
+{
+    int c;
+
+    c = fgetc(fp);
+    if (c == EOF) {
+        return 1;
+    } else {
+        ungetc(c, fp);
+        return 0;
+    }
+}
+
+/**
+ * @brief Open the @p filepath, parse patterns and given string-argument.
+ *
+ * @param[in] filepath File to parse. Contains patterns and string.
+ * @param[out] infile The file descriptor of @p filepath.
+ * @param[out] patterns Storage of patterns.
+ * @param[out] patterns_count Number of items in @p patterns.
+ * @param[out] strarg The string-argument to check.
+ * @return 0 on success.
+ */
+static int
+parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
+{
+    int blankline = 0;
+    char *str = NULL;
+    size_t len = 0;
+    ssize_t l;
+
+    *infile = fopen(filepath, "rb");
+    if (!(*infile)) {
+        fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
+        goto error;
+    }
+    if (file_is_empty(*infile)) {
+        if (create_empty_string(strarg)) {
+            goto error;
+        }
+        return 0;
+    }
+
+    while ((l = getline(&str, &len, *infile)) != -1) {
+        if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
+            /* blank line */
+            blankline = 1;
+            continue;
+        }
+        if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
+            /* remove ending newline */
+            if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
+                str[l - 2] = '\0';
+            } else {
+                str[l - 1] = '\0';
+            }
+        }
+        if (blankline) {
+            /* done - str is now the string to check */
+            blankline = 0;
+            *strarg = str;
+            break;
+            /* else read the patterns */
+        } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
+            goto error;
+        }
+        if (str[0] == ' ') {
+            /* set invert-match */
+            (*patterns)[*patterns_count - 1].invert = 1;
+        }
+    }
+    if (!str || (blankline && (str[0] != '\0'))) {
+        /* corner case, no input after blankline meaning the pattern to check is empty */
+        if (create_empty_string(&str)) {
+            goto error;
+        }
+    }
+    *strarg = str;
+
+    return 0;
+
+error:
+    free(str);
+    if (*infile) {
+        fclose(*infile);
+        *infile = NULL;
+    }
+    *strarg = NULL;
+
+    return 1;
+}
+
+static char *
+modstr_init(void)
+{
+    const char *module_start = "module yangre {"
+            "yang-version 1.1;"
+            "namespace urn:cesnet:libyang:yangre;"
+            "prefix re;"
+            "leaf pattern {"
+            "  type string {";
+
+    return strdup(module_start);
+}
+
+static char *
+modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
+{
+    char *new;
+    const char *module_invertmatch = " { modifier invert-match; }";
+    const char *module_match = ";";
+
+    if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
+            pattern->invert ? module_invertmatch : module_match) == -1) {
+        fprintf(stderr, "yangre error: memory allocation failed.\n");
+        return NULL;
+    }
+    free(*modstr);
+    *modstr = NULL;
+
+    return new;
+}
+
+static char *
+modstr_add_ending(char **modstr)
+{
+    char *new;
+    static const char *module_end = "}}}";
+
+    if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
+        fprintf(stderr, "yangre error: memory allocation failed.\n");
+        return NULL;
+    }
+    free(*modstr);
+    *modstr = NULL;
+
+    return new;
+}
+
+static int
+create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
+{
+    int i;
+    char *new = NULL, *modstr;
+
+    if (!(modstr = modstr_init())) {
+        goto error;
+    }
+
+    for (i = 0; i < patterns_count; i++) {
+        if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
+            goto error;
+        }
+        modstr = new;
+    }
+
+    if (!(new = modstr_add_ending(&modstr))) {
+        goto error;
+    }
+
+    *mod = new;
+
+    return 0;
+
+error:
+    *mod = NULL;
+    free(new);
+    free(modstr);
+
+    return 1;
+}
+
+static void
+print_verbose(struct ly_ctx *ctx, struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
+{
+    int i;
+
+    for (i = 0; i < patterns_count; i++) {
+        fprintf(stdout, "pattern  %d: %s\n", i + 1, patterns[i].expr);
+        fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
+    }
+    fprintf(stdout, "string    : %s\n", str);
+    if (match == LY_SUCCESS) {
+        fprintf(stdout, "result    : matching\n");
+    } else if (match == LY_EVALID) {
+        fprintf(stdout, "result    : not matching\n");
+    } else {
+        fprintf(stdout, "result    : error (%s)\n", ly_errmsg(ctx));
+    }
 }
 
 int
 main(int argc, char *argv[])
 {
     LY_ERR match;
-    int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0;
+    int i, opt_index = 0, ret = 1, verbose = 0;
     struct option options[] = {
         {"help",             no_argument,       NULL, 'h'},
         {"file",             required_argument, NULL, 'f'},
@@ -120,21 +333,20 @@
         {"verbose",          no_argument,       NULL, 'V'},
         {NULL,               0,                 NULL, 0}
     };
-    char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
-    int *invert_match = NULL;
+    struct yr_pattern *patterns = NULL;
+    char *str = NULL, *modstr = NULL;
     int patterns_count = 0;
     struct ly_ctx *ctx = NULL;
     struct lys_module *mod;
     FILE *infile = NULL;
-    size_t len = 0;
-    ssize_t l;
+    ly_bool info_printed = 0;
 
     opterr = 0;
     while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
         switch (i) {
         case 'h':
             help();
-            ret = -2; /* continue to allow printing version and help at once */
+            info_printed = 1;
             break;
         case 'f':
             if (infile) {
@@ -146,52 +358,17 @@
                 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
                 goto cleanup;
             }
-            infile = fopen(optarg, "rb");
-            if (!infile) {
-                fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
+            if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
                 goto cleanup;
             }
-
-            while ((l = getline(&str, &len, infile)) != -1) {
-                if (!blankline && (str[0] == '\n')) {
-                    /* blank line */
-                    blankline = 1;
-                    continue;
-                }
-                if ((str[0] != '\n') && (str[l - 1] == '\n')) {
-                    /* remove ending newline */
-                    str[l - 1] = '\0';
-                }
-                if (blankline) {
-                    /* done - str is now the string to check */
-                    blankline = 0;
-                    break;
-                    /* else read the patterns */
-                } else if (add_pattern(&patterns, &invert_match, &patterns_count,
-                        (str[0] == ' ') ? &str[1] : str)) {
-                    goto cleanup;
-                }
-                if (str[0] == ' ') {
-                    /* set invert-match */
-                    invert_match[patterns_count - 1] = 1;
-                }
-            }
-            if (blankline) {
-                /* corner case, no input after blankline meaning the pattern to check is empty */
-                if (str != NULL) {
-                    free(str);
-                }
-                str = malloc(sizeof(char));
-                str[0] = '\0';
-            }
             break;
         case 'i':
-            if (!patterns_count || invert_match[patterns_count - 1]) {
+            if (!patterns_count || patterns[patterns_count - 1].invert) {
                 help();
                 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
                 goto cleanup;
             }
-            invert_match[patterns_count - 1] = 1;
+            patterns[patterns_count - 1].invert = 1;
             break;
         case 'p':
             if (infile) {
@@ -199,13 +376,13 @@
                 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
                 goto cleanup;
             }
-            if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) {
+            if (add_pattern(&patterns, &patterns_count, optarg)) {
                 goto cleanup;
             }
             break;
         case 'v':
             version();
-            ret = -2; /* continue to allow printing version and help at once */
+            info_printed = 1;
             break;
         case 'V':
             verbose = 1;
@@ -221,7 +398,8 @@
         }
     }
 
-    if (ret == -2) {
+    if (info_printed) {
+        ret = 0;
         goto cleanup;
     }
 
@@ -239,24 +417,9 @@
         str = argv[optind];
     }
 
-    for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
-        if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
-            fprintf(stderr, "yangre error: memory allocation failed.\n");
-            goto cleanup;
-        }
-        if (modstr != module_start) {
-            free(modstr);
-        }
-        modstr = s;
-    }
-    if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
-        fprintf(stderr, "yangre error: memory allocation failed.\n");
+    if (create_module(patterns, patterns_count, &modstr)) {
         goto cleanup;
     }
-    if (modstr != module_start) {
-        free(modstr);
-    }
-    modstr = s;
 
     if (ly_ctx_new(NULL, 0, &ctx)) {
         goto cleanup;
@@ -271,34 +434,24 @@
     match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
 
     if (verbose) {
-        for (i = 0; i < patterns_count; i++) {
-            fprintf(stdout, "pattern  %d: %s\n", i + 1, patterns[i]);
-            fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular");
-        }
-        fprintf(stdout, "string    : %s\n", str);
-        if (match == LY_SUCCESS) {
-            fprintf(stdout, "result    : matching\n");
-        } else if (match == LY_EVALID) {
-            fprintf(stdout, "result    : not matching\n");
-        } else {
-            fprintf(stdout, "result    : error (%s)\n", ly_errmsg(ctx));
-        }
+        print_verbose(ctx, patterns, patterns_count, str, match);
     }
     if (match == LY_SUCCESS) {
         ret = 0;
     } else if (match == LY_EVALID) {
-        ret = 1;
+        ret = 2;
     } else {
-        ret = -1;
+        ret = 1;
     }
 
 cleanup:
     ly_ctx_destroy(ctx);
     for (i = 0; i < patterns_count; i++) {
-        free(patterns[i]);
+        free(patterns[i].expr);
     }
-    free(patterns);
-    free(invert_match);
+    if (patterns_count) {
+        free(patterns);
+    }
     free(modstr);
     if (infile) {
         fclose(infile);