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

#!/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