Skip to content

Commit

Permalink
Adding linter for IDL
Browse files Browse the repository at this point in the history
Removing current warnings from parser and create a new file
which will traverse AST after it is built looking for possible
issues.

Also includes minor comment cleanup for visitor class on which
the linter is based.


TEST= python idl_parser.py
BUG= http://code.google.com/p/chromium/issues/detail?id=76271

R= sehr@google.com
Review URL: http://codereview.chromium.org/7468012

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@93655 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
noelallen@google.com committed Jul 22, 2011
1 parent 05d7cf8 commit caff0c3
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 28 deletions.
120 changes: 120 additions & 0 deletions ppapi/generators/idl_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/python
#
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

""" Lint for IDL """

import os
import sys

from idl_log import ErrOut, InfoOut, WarnOut
from idl_node import IDLAttribute, IDLNode
from idl_ast import IDLAst
from idl_option import GetOption, Option, ParseOptions
from idl_outfile import IDLOutFile
from idl_visitor import IDLVisitor


Option('wcomment', 'Disable warning for missing comment.')
Option('wenum', 'Disable warning for missing enum value.')
Option('winline', 'Disable warning for inline blocks.')
Option('wparam', 'Disable warning for missing [in|out|inout] on param.')
Option('wpass', 'Disable warning for mixed passByValue and returnByValue.')

#
# IDLLinter
#
# Once the AST is build, we need to resolve the namespace and version
# information.
#
class IDLLinter(IDLVisitor):
def VisitFilter(self, node, data):
__pychecker__ = 'unusednames=node,data'
return not node.IsA('Comment', 'Copyright')

def Arrive(self, node, errors):
__pychecker__ = 'unusednames=node,errors'
warnings = 0
if node.IsA('Interface', 'Member', 'Struct', 'Enum', 'EnumItem', 'Typedef'):
comments = node.GetListOf('Comment')
if not comments and not node.GetProperty('wcomment'):
node.Warning('Expecting a comment.')
warnings += 1

if node.IsA('Struct', 'Typedef') and not node.GetProperty('wpass'):
if node.GetProperty('passByValue'):
pbv = 'is'
else:
pbv = 'is not'
if node.GetProperty('returnByValue'):
ret = 'is'
else:
ret = 'is not'
if pbv != ret:
node.Warning('%s passByValue but %s returnByValue.' % (pbv, ret))
warnings += 1

if node.IsA('EnumItem'):
if not node.GetProperty('VALUE') and not node.GetProperty('wenum'):
node.Warning('Expecting value for enumeration.')
warnings += 1

if node.IsA('Interface'):
if not node.GetLabel():
node.Warning('Expecting label.')
warnings += 1
macro = node.GetProperty('macro')
if macro:
node.Warning('Interface name inconsistent: %s' % macro)
warnings += 1

if node.IsA('Inline') and not node.GetProperty('winline'):
inline_type = node.GetProperty('NAME')
node.parent.Warning('Requires an inline %s block.' % inline_type)
warnings += 1

if node.IsA('Callspec'):
out = False
for arg in node.GetListOf('Param'):
if arg.GetProperty('out'):
out = True
if arg.GetProperty('in') and out:
arg.Warning('[in] parameter after [out] parameter')
warnings += 1

if node.IsA('Param') and not node.GetProperty('wparam'):
found = False;
for form in ['in', 'inout', 'out']:
if node.GetProperty(form): found = True
if not found:
node.Warning('Missing argument type: [in|out|inout]')
warnings += 1

return warnings

def Depart(self, node, warnings, childdata):
__pychecker__ = 'unusednames=node'
for child in childdata:
warnings += child
return warnings

def Lint(ast):
if GetOption('wcomment'): ast.SetProperty('wcomment', True)
if GetOption('wenum'): ast.SetProperty('wenum', True)
if GetOption('winline'): ast.SetProperty('winilne', True)
if GetOption('wparam'): ast.SetProperty('wparam', True)
if GetOption('wpass'): ast.SetProperty('wpass', True)

skipList = []
for filenode in ast.GetListOf('File'):
name = filenode.GetProperty('NAME')
if filenode.GetProperty('ERRORS') > 0:
ErrOut.Log('%s : Skipped due to errors.', name)
skipList.append(filenode)
continue
warnings = IDLLinter().Visit(filenode, 0)
if warnings:
WarnOut.Log('%s warning(s) for %s\n' % (warnings, name))
return skipList
26 changes: 3 additions & 23 deletions ppapi/generators/idl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from idl_lexer import IDLLexer
from idl_node import IDLAttribute, IDLFile, IDLNode
from idl_option import GetOption, Option, ParseOptions
from idl_lint import Lint

from ply import lex
from ply import yacc
Expand All @@ -44,8 +45,6 @@
Option('token_debug', 'Debug token generation.')
Option('dump_tree', 'Dump the tree.')
Option('srcroot', 'Working directory.', default='../api')
Option('wcomment', 'Disable warning for missing comment.')
Option('wenum', 'Disable warning for missing enum value.')

#
# ERROR_REMAP
Expand Down Expand Up @@ -709,26 +708,6 @@ def token(self):
InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value))
return tok

#
# VerifyProduction
#
# Once the node is built, we will check for certain types of issues
#
def VerifyProduction(self, node):
comment = node.GetOneOf('Comment')
if node.cls in ['Interface', 'Struct', 'Member'] and not comment:
if not GetOption('wcomment') and not node.GetProperty('wcomment'):
self.Warn(node, 'Missing comment for %s.' % node)
elif node.cls in ['EnumItem'] and not node.GetProperty('VALUE'):
if not GetOption('wenum'):
self.Warn(node, 'Missing value for enumeration %s.' % node)
elif node.cls in ['Param']:
found = False;
for form in ['in', 'inout', 'out']:
if node.GetProperty(form): found = True
if not found: self.Warn(node, 'Missing argument type: [in|out|inout]')


#
# BuildProduction
#
Expand All @@ -747,7 +726,6 @@ def BuildProduction(self, cls, p, index, childlist=None):
out = IDLNode(cls, filename, lineno, pos, childlist)
if self.build_debug:
InfoOut.Log("Building %s" % out)
self.VerifyProduction(out)
return out

def BuildNamed(self, cls, p, index, childlist=None):
Expand Down Expand Up @@ -1010,6 +988,8 @@ def ParseFiles(filenames):

ast = IDLAst(filenodes)
if GetOption('dump_tree'): ast.Dump(0)

Lint(ast)
return ast


Expand Down
14 changes: 9 additions & 5 deletions ppapi/generators/idl_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
# IDLVisitor
#
# The IDLVisitor class will traverse an AST truncating portions of the tree
# that fail due to class or version filters. For each node, after the filter
# passes, the visitor will call the 'Arive' member passing in the node and
# and data passing in from the parent call. It will then Visit the children.
# When done processing children, the visitor will call the 'Depart' member
# before returning
# when 'VisitFilter' returns false. After the filter returns true, for each
# node, the visitor will call the 'Arrive' member passing in the node and
# and generic data object from the parent call. The returned value is then
# passed to all children who's results are aggregated into a list. The child
# results along with the original Arrive result are passed to the Depart
# function which returns the final result of the Visit. By default this is
# the exact value that was return from the original arrive.
#

class IDLVisitor(object):
Expand Down Expand Up @@ -45,9 +47,11 @@ def Visit(self, node, data):
return out

def Arrive(self, node, data):
__pychecker__ = 'unusednames=node'
return data

def Depart(self, node, data, childdata):
__pychecker__ = 'unusednames=node,childdata'
return data


Expand Down

0 comments on commit caff0c3

Please sign in to comment.