mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
7.9 KiB
238 lines
7.9 KiB
#!/usr/bin/env python
|
|
#
|
|
# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS-IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Parser for JavaScript files."""
|
|
|
|
|
|
|
|
from closure_linter import javascripttokens
|
|
from closure_linter import statetracker
|
|
from closure_linter import tokenutil
|
|
|
|
# Shorthand
|
|
Type = javascripttokens.JavaScriptTokenType
|
|
|
|
|
|
class JsDocFlag(statetracker.DocFlag):
|
|
"""Javascript doc flag object.
|
|
|
|
Attribute:
|
|
flag_type: param, return, define, type, etc.
|
|
flag_token: The flag token.
|
|
type_start_token: The first token specifying the flag JS type,
|
|
including braces.
|
|
type_end_token: The last token specifying the flag JS type,
|
|
including braces.
|
|
type: The JavaScript type spec.
|
|
name_token: The token specifying the flag name.
|
|
name: The flag name
|
|
description_start_token: The first token in the description.
|
|
description_end_token: The end token in the description.
|
|
description: The description.
|
|
"""
|
|
|
|
# Please keep these lists alphabetized.
|
|
|
|
# Some projects use the following extensions to JsDoc.
|
|
# TODO(robbyw): determine which of these, if any, should be illegal.
|
|
EXTENDED_DOC = frozenset([
|
|
'class', 'code', 'desc', 'final', 'hidden', 'inheritDoc', 'link',
|
|
'protected', 'notypecheck', 'throws'])
|
|
|
|
LEGAL_DOC = EXTENDED_DOC | statetracker.DocFlag.LEGAL_DOC
|
|
|
|
def __init__(self, flag_token):
|
|
"""Creates the JsDocFlag object and attaches it to the given start token.
|
|
|
|
Args:
|
|
flag_token: The starting token of the flag.
|
|
"""
|
|
statetracker.DocFlag.__init__(self, flag_token)
|
|
|
|
|
|
class JavaScriptStateTracker(statetracker.StateTracker):
|
|
"""JavaScript state tracker.
|
|
|
|
Inherits from the core EcmaScript StateTracker adding extra state tracking
|
|
functionality needed for JavaScript.
|
|
"""
|
|
|
|
def __init__(self, closurized_namespaces=''):
|
|
"""Initializes a JavaScript token stream state tracker.
|
|
|
|
Args:
|
|
closurized_namespaces: An optional list of namespace prefixes used for
|
|
testing of goog.provide/require.
|
|
"""
|
|
statetracker.StateTracker.__init__(self, JsDocFlag)
|
|
self.__closurized_namespaces = closurized_namespaces
|
|
|
|
def Reset(self):
|
|
"""Resets the state tracker to prepare for processing a new page."""
|
|
super(JavaScriptStateTracker, self).Reset()
|
|
|
|
self.__goog_require_tokens = []
|
|
self.__goog_provide_tokens = []
|
|
self.__provided_namespaces = set()
|
|
self.__used_namespaces = []
|
|
|
|
def InTopLevel(self):
|
|
"""Compute whether we are at the top level in the class.
|
|
|
|
This function call is language specific. In some languages like
|
|
JavaScript, a function is top level if it is not inside any parenthesis.
|
|
In languages such as ActionScript, a function is top level if it is directly
|
|
within a class.
|
|
|
|
Returns:
|
|
Whether we are at the top level in the class.
|
|
"""
|
|
return not self.InParentheses()
|
|
|
|
def GetGoogRequireTokens(self):
|
|
"""Returns list of require tokens."""
|
|
return self.__goog_require_tokens
|
|
|
|
def GetGoogProvideTokens(self):
|
|
"""Returns list of provide tokens."""
|
|
return self.__goog_provide_tokens
|
|
|
|
def GetProvidedNamespaces(self):
|
|
"""Returns list of provided namespaces."""
|
|
return self.__provided_namespaces
|
|
|
|
def GetUsedNamespaces(self):
|
|
"""Returns list of used namespaces, is a list of sequences."""
|
|
return self.__used_namespaces
|
|
|
|
def GetBlockType(self, token):
|
|
"""Determine the block type given a START_BLOCK token.
|
|
|
|
Code blocks come after parameters, keywords like else, and closing parens.
|
|
|
|
Args:
|
|
token: The current token. Can be assumed to be type START_BLOCK
|
|
Returns:
|
|
Code block type for current token.
|
|
"""
|
|
last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, None,
|
|
True)
|
|
if last_code.type in (Type.END_PARAMETERS, Type.END_PAREN,
|
|
Type.KEYWORD) and not last_code.IsKeyword('return'):
|
|
return self.CODE
|
|
else:
|
|
return self.OBJECT_LITERAL
|
|
|
|
def HandleToken(self, token, last_non_space_token):
|
|
"""Handles the given token and updates state.
|
|
|
|
Args:
|
|
token: The token to handle.
|
|
last_non_space_token:
|
|
"""
|
|
super(JavaScriptStateTracker, self).HandleToken(token,
|
|
last_non_space_token)
|
|
|
|
if token.IsType(Type.IDENTIFIER):
|
|
if token.string == 'goog.require':
|
|
class_token = tokenutil.Search(token, Type.STRING_TEXT)
|
|
self.__goog_require_tokens.append(class_token)
|
|
|
|
elif token.string == 'goog.provide':
|
|
class_token = tokenutil.Search(token, Type.STRING_TEXT)
|
|
self.__goog_provide_tokens.append(class_token)
|
|
|
|
elif self.__closurized_namespaces:
|
|
self.__AddUsedNamespace(token.string)
|
|
if token.IsType(Type.SIMPLE_LVALUE) and not self.InFunction():
|
|
identifier = token.values['identifier']
|
|
|
|
if self.__closurized_namespaces:
|
|
namespace = self.GetClosurizedNamespace(identifier)
|
|
if namespace and identifier == namespace:
|
|
self.__provided_namespaces.add(namespace)
|
|
if (self.__closurized_namespaces and
|
|
token.IsType(Type.DOC_FLAG) and
|
|
token.attached_object.flag_type == 'implements'):
|
|
# Interfaces should be goog.require'd.
|
|
doc_start = tokenutil.Search(token, Type.DOC_START_BRACE)
|
|
interface = tokenutil.Search(doc_start, Type.COMMENT)
|
|
self.__AddUsedNamespace(interface.string)
|
|
|
|
def __AddUsedNamespace(self, identifier):
|
|
"""Adds the namespace of an identifier to the list of used namespaces.
|
|
|
|
Args:
|
|
identifier: An identifier which has been used.
|
|
"""
|
|
namespace = self.GetClosurizedNamespace(identifier)
|
|
|
|
if namespace:
|
|
# We add token.string as a 'namespace' as it is something that could
|
|
# potentially be provided to satisfy this dependency.
|
|
self.__used_namespaces.append([namespace, identifier])
|
|
|
|
def GetClosurizedNamespace(self, identifier):
|
|
"""Given an identifier, returns the namespace that identifier is from.
|
|
|
|
Args:
|
|
identifier: The identifier to extract a namespace from.
|
|
|
|
Returns:
|
|
The namespace the given identifier resides in, or None if one could not
|
|
be found.
|
|
"""
|
|
parts = identifier.split('.')
|
|
for part in parts:
|
|
if part.endswith('_'):
|
|
# Ignore private variables / inner classes.
|
|
return None
|
|
|
|
if identifier.startswith('goog.global'):
|
|
# Ignore goog.global, since it is, by definition, global.
|
|
return None
|
|
|
|
for namespace in self.__closurized_namespaces:
|
|
if identifier.startswith(namespace + '.'):
|
|
last_part = parts[-1]
|
|
if not last_part:
|
|
# TODO(robbyw): Handle this: it's a multi-line identifier.
|
|
return None
|
|
|
|
if last_part in ('apply', 'inherits', 'call'):
|
|
# Calling one of Function's methods usually indicates use of a
|
|
# superclass.
|
|
parts.pop()
|
|
last_part = parts[-1]
|
|
|
|
for i in xrange(1, len(parts)):
|
|
part = parts[i]
|
|
if part.isupper():
|
|
# If an identifier is of the form foo.bar.BAZ.x or foo.bar.BAZ,
|
|
# the namespace is foo.bar.
|
|
return '.'.join(parts[:i])
|
|
if part == 'prototype':
|
|
# If an identifier is of the form foo.bar.prototype.x, the
|
|
# namespace is foo.bar.
|
|
return '.'.join(parts[:i])
|
|
|
|
if last_part.isupper() or not last_part[0].isupper():
|
|
# Strip off the last part of an enum or constant reference.
|
|
parts.pop()
|
|
|
|
return '.'.join(parts)
|
|
|
|
return None
|
|
|