-
-
Notifications
You must be signed in to change notification settings - Fork 236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: static variable analysis #770
base: master
Are you sure you want to change the base?
Conversation
Pull Request Test Coverage Report for Build 11894990314Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
|
||
expect(analysis).toStrictEqual({ | ||
variables: { 'd[a[b.c]]': [d], 'a[b.c]': [a], 'b.c': [bc] }, | ||
globals: { 'd[a[b.c]]': [d], 'a[b.c]': [a], 'b.c': [bc] }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if d
, a
are global while b
is local? Not sure if we can implement it thoroughly. If not, would you consider to reduce this feature to a list of top level variables:
globals: ['d', 'a', 'b']
I guess this list is already useful for new comers, am I correct here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a test with a local variable nested within a global variable. Still not sure what's best here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you consider my proposal in another comment? I assume dynamic index is not important as users won't figure out its value in runtime (not sure, do you actually have such a use case that I'm missing out?). Thus we can simplize the situation. For references:
a[b.c].d
a[0].c
We return
a[].d
b.c
a.0.c or a[0].c // considering `a.0.c` can be confusion when 0 is a string (which can contain dots), latter maybe better as you have a `variableSegments` for easier consumption anyway, this is only for inspection I guess
Note: static index like 0
or quoted strings can still be treated statically, like property access.
With the latest commit, I've changed several of the built-in tags to fix their row and column numbers reported from I guess this is a good point to decide if having correct row and column is important enough to warrant these changes. Notice that when parsing |
I've provisionally implemented some convenience analysis methods on the Other ideas that I've yet to implement:
|
@harttle 👋 , I've not forgotten about this or your previous comments about keeping track of aliases. I'll get back to it soon. |
] | ||
``` | ||
|
||
Or use `Liquid.variableSegments(template)` to get an array of strings and numbers that make up each variable's path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For fullVariables
and variableSegments
, can we also include an examle for nested variables, or we'll need to mention how nesting will be handled in return values of these 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference a[b.c].d
, I think we have another option, return 2 references without nesting them. As what's inside []
is not important because it's dynamic anyway:
a[].d
b.c
If you adopt this implementation, we'll need to decide how to represent []
in variableSegments
return value. Otherwise these 2 will be the same:
arr[0].length
arr.length
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe differentiate with nesting like:
['arr', ['length']]
['arr', 'length']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using your example, a[b.c].d
, variableSegments
was incorrectly producing something like this:
[
[
'a',
Variable {
segments: [ 'b', 'c' ],
location: { row: 1, col: 6, file: undefined }
},
'd'
],
[ 'b', 'c' ]
]
This was not my intention. Now we get the following.
variables
[ 'a', 'b' ]
fullVariables
[
'a[b.c].d',
'b.c'
]`
variableSegments
[
[ 'a', [ 'b', 'c' ], 'd' ],
[ 'b', 'c' ]
]
For arr[0].length
and arr.length
, we get [ 'arr', 0, 'length' ]
and [ 'arr', 'length' ]
, respectively.
Is that what you had in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I mean maybe we can drop nesting. And treat them as different references. I guess what exactly is inside ’[]’ is not important, we only know that array/map is accessed. Assuming figure out its value when called is not feasible for static analysis, then ppl won’t use that information effectively. Here’s my simplified approach
variables
[ 'a', 'b' ]
fullVariables
[
'a[].d',
'b.c'
]`
variableSegments
[
[ 'a', [ 'd' ] ],
[ 'b', 'c' ]
]
note the last one use nested array to represent entering into array, not nested index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. I think your approach should be in addition to variableSegments
, as separate method/s. Then users have the option of working with nested variables and array indexes, if they're useful, or your more convenient representation if they're not.
Perhaps normalizedSegments
would be a good name?
It seems like we're loosing potentially valuable information if we drop nested variables altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just another idea to simplify the implementation. If you still think dynamic index info is important, we can keep your current implementation. No need to compromise for my opinion.
Can you think of better names for At the moment:
|
|
||
expect(analysis).toStrictEqual({ | ||
variables: { 'd[a[b.c]]': [d], 'a[b.c]': [a], 'b.c': [bc] }, | ||
globals: { 'd[a[b.c]]': [d], 'a[b.c]': [a], 'b.c': [bc] }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you consider my proposal in another comment? I assume dynamic index is not important as users won't figure out its value in runtime (not sure, do you actually have such a use case that I'm missing out?). Thus we can simplize the situation. For references:
a[b.c].d
a[0].c
We return
a[].d
b.c
a.0.c or a[0].c // considering `a.0.c` can be confusion when 0 is a string (which can contain dots), latter maybe better as you have a `variableSegments` for easier consumption anyway, this is only for inspection I guess
Note: static index like 0
or quoted strings can still be treated statically, like property access.
@@ -0,0 +1,286 @@ | |||
--- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you also need change sidebar and en.yml, to make this file visible on sidebar.
] | ||
``` | ||
|
||
Or use `Liquid.variableSegments(template)` to get an array of strings and numbers that make up each variable's path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I mean maybe we can drop nesting. And treat them as different references. I guess what exactly is inside ’[]’ is not important, we only know that array/map is accessed. Assuming figure out its value when called is not feasible for static analysis, then ppl won’t use that information effectively. Here’s my simplified approach
variables
[ 'a', 'b' ]
fullVariables
[
'a[].d',
'b.c'
]`
variableSegments
[
[ 'a', [ 'd' ] ],
[ 'b', 'c' ]
]
note the last one use nested array to represent entering into array, not nested index.
your current naming is OK for me. Sorry some comments are not submitted yesterday, causing confusion. |
Statically analyze templates and report variable usage.
Usage
Retrieve the names of variables used in a template with
Liquid.variables(template)
. It returns an array of strings, one string for each distinct variable, without its properties.Output
Alternatively, use
Liquid.fullVariables(template)
to get a list of variables including their properties. Notice that variables from tag and filter arguments are included too.Output
Or use
Liquid.variableSegments(template)
to get an array of strings and numbers that make up each variable's path.Output
Global Variables
Notice, in the examples above, that
title
andemail
are included in the results. Often you'll want to exclude names that are in scope from{% assign %}
tags, and temporary variables like those introduced by a{% for %}
tag.To get names that are expected to be global, that is, provided by application developers rather than template authors, use the
globalVariables
,globalFullVariables
orglobalVariableSegments
methods (or their synchronous equivalents) of aLiquid
class instance.Output
Partial Templates
By default, LiquidJS will try to load and analyze any included and rendered templates too.
Output
You can disable analysis of partial templates by setting the
partials
options tofalse
.Output
If an
{% include %}
tag uses a dynamic template name (one that can't be determined without rendering the template) it will be ignored, even ifpartials
is set totrue
.